Skip to content

Commit

Permalink
feat: Add Echo App end-to-end test
Browse files Browse the repository at this point in the history
  • Loading branch information
fmoura committed Mar 22, 2024
1 parent a728248 commit 1b71aa9
Show file tree
Hide file tree
Showing 9 changed files with 532 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for `CARTESI_AUTH_PRIVATE_KEY` and `CARTESI_AUTH_PRIVATE_KEY_FILE`
- Added `CARTESI_AUTH_KIND` environment variable to select the blockchain authetication method
- Added structured logging with slog. Colored logs can now be enabled with `CARTESI_LOG_PRETTY` environment variable.
- Added Rollps end to end test using Echo Dapp

### Changed

Expand Down
1 change: 1 addition & 0 deletions cmd/cartesi-rollups-cli/root/send/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func init() {
}

func run(cmd *cobra.Command, args []string) {

payload, err := hexutil.Decode(hexPayload)
cobra.CheckErr(err)

Expand Down
49 changes: 49 additions & 0 deletions end-to-end-tests/client_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

package endtoendtests

import (
"context"

"github.com/cartesi/rollups-node/pkg/addresses"
"github.com/cartesi/rollups-node/pkg/ethutil"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
)

func AddInput(ctx context.Context,
blockchainHttpEnpoint string, payload string) (int, error) {

// Send Input
client, err := ethclient.DialContext(ctx, blockchainHttpEnpoint)
if err != nil {
return 0, err
}
defer client.Close()

signer, err := ethutil.NewMnemonicSigner(ctx, client, ethutil.FoundryMnemonic, 0)
if err != nil {
return 0, err
}
book := addresses.GetTestBook()

payloadBytes, _ := hexutil.Decode(payload)
inputIndex, err := ethutil.AddInput(ctx, client, book, signer, payloadBytes)
if err != nil {
return 0, err
}
return inputIndex, nil

}

func AdvanceTime(ctx context.Context, blockchainHttpEnpoint string, timeInSeconds int) error {
client, err := rpc.DialContext(ctx, blockchainHttpEnpoint)
if err != nil {
return err
}
defer client.Close()
return client.CallContext(ctx, nil, "evm_increaseTime", timeInSeconds)

}
85 changes: 85 additions & 0 deletions end-to-end-tests/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

// Package endtoendtests
package endtoendtests

import (
"fmt"
"log/slog"
"time"

"github.com/cartesi/rollups-node/internal/node/config"
"github.com/cartesi/rollups-node/pkg/addresses"
"github.com/cartesi/rollups-node/pkg/ethutil"
)

const (
LocalPostgresEndpoint = "postgres://postgres:password@localhost:5432/postgres"
LocalBlockchainID = 31337
LocalBlockchainHttpEndpoint = "http://localhost:8545"
LocalBlockchainWsEnpoint = "ws://localhost:8545"
LocalApplicationDeploymentBlockNumber = 20
LocalInputBoxDeploymentBlockNumber = 20
LocalHttpAddress = "0.0.0.0"
LocalHttpPort = 10000
LocalBlockTimeout = 60
LocalFinalityOffset = 1
LocalEpochDurationInSeconds = 120
)

func NewLocalNodeConfig() config.NodeConfig {
var nodeConfig config.NodeConfig

book := addresses.GetTestBook()

//Log
nodeConfig.LogLevel = slog.LevelInfo
nodeConfig.LogPretty = false

//Postgres
nodeConfig.PostgresEndpoint =
config.Redacted[string]{Value: LocalPostgresEndpoint}

//Epoch
nodeConfig.RollupsEpochDuration, _ =
time.ParseDuration(fmt.Sprintf("%ds", LocalEpochDurationInSeconds))

//Blochain
nodeConfig.BlockchainID = LocalBlockchainID
nodeConfig.BlockchainHttpEndpoint =
config.Redacted[string]{Value: LocalBlockchainHttpEndpoint}
nodeConfig.BlockchainWsEndpoint =
config.Redacted[string]{Value: LocalBlockchainWsEnpoint}
nodeConfig.BlockchainIsLegacy = false
nodeConfig.BlockchainFinalityOffset = LocalFinalityOffset
nodeConfig.BlockchainBlockTimeout = LocalBlockTimeout

//Contracts
nodeConfig.ContractsHistoryAddress = book.HistoryAddress.Hex()
nodeConfig.ContractsAuthorityAddress = book.AuthorityAddress.Hex()
nodeConfig.ContractsApplicationAddress = book.CartesiDApp.Hex()
nodeConfig.ContractsApplicationDeploymentBlockNumber = LocalApplicationDeploymentBlockNumber
nodeConfig.ContractsInputBoxAddress = book.InputBox.Hex()
nodeConfig.ContractsInputBoxDeploymentBlockNumber = LocalInputBoxDeploymentBlockNumber

//HTTP endpoint
nodeConfig.HttpAddress = LocalHttpAddress
nodeConfig.HttpPort = LocalHttpPort

//Features
nodeConfig.FeatureHostMode = false
nodeConfig.FeatureDisableClaimer = false
nodeConfig.FeatureDisableMachineHashCheck = true

//Experimental
nodeConfig.ExperimentalSunodoValidatorEnabled = false

//Auth
nodeConfig.Auth = config.AuthMnemonic{
Mnemonic: config.Redacted[string]{Value: ethutil.FoundryMnemonic},
AccountIndex: config.Redacted[int]{Value: 0},
}

return nodeConfig
}
246 changes: 246 additions & 0 deletions end-to-end-tests/echo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

//go:build endtoendtests
// +build endtoendtests

package endtoendtests

import (
"context"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/Khan/genqlient/graphql"
"github.com/cartesi/rollups-node/internal/deps"
"github.com/cartesi/rollups-node/internal/machine"
"github.com/cartesi/rollups-node/internal/node"
"github.com/cartesi/rollups-node/pkg/readerclient"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)

const (
payload = "0xdeadbeef"
maxReadInputAttempts = 10
defaulTxDatabaseFile = "default_tx_database"
)

type EchoInputTestSuite struct {
suite.Suite
containers *deps.DepsContainers
cancel context.CancelFunc
tempDir string
supervisorErr chan error
}

func (s *EchoInputTestSuite) SetupSuite() {

// Clear default_tx_database
cwd, err := os.Getwd()
require.NoError(s.T(), err)

_ = os.Remove(filepath.Join(cwd, defaulTxDatabaseFile))

// Create machine snapshot
tempDir, err := ioutil.TempDir("", "machine-snapshot")
assert.NoError(s.T(), err)
machine.Save("cartesi/rollups-node-snapshot:devel", tempDir, "test-echo-app")

// Run deps
ctx := context.Background()
var depsConfig = deps.NewDefaultDepsConfig()
depsContainers, err := deps.Run(ctx, *depsConfig)
assert.NoError(s.T(), err)

// Wait a little for Dependencies to be actually ready
time.Sleep(2 * time.Second)

// Run Node Service
nodeConfig := NewLocalNodeConfig()

nodeConfig.SnapshotDir = tempDir

ctx, cancel := context.WithCancel(ctx)
supervisor, err := node.Setup(ctx, nodeConfig)
require.NoError(s.T(), err)

ready := make(chan struct{}, 1)
supervisorErr := make(chan error, 1)
go func() {
err := supervisor.Start(ctx, ready)
if err != nil {
supervisorErr <- err
}
}()

select {
case err := <-supervisorErr:
require.NoError(s.T(), err)
case <-ready:
break
}

// Configure Suite for tear down
s.containers = depsContainers
s.tempDir = tempDir
s.cancel = cancel
s.supervisorErr = supervisorErr

}

func (s *EchoInputTestSuite) TearDownSuite() {

// Stop Node services
s.cancel()

// Remove machine snpshot
os.RemoveAll(s.tempDir)

// Terminate deps
ctx := context.Background()
err := deps.Terminate(ctx, s.containers)
require.NoError(s.T(), err)

// Clear default_tx_database
cwd, err := os.Getwd()
require.NoError(s.T(), err)

_ = os.Remove(filepath.Join(cwd, defaulTxDatabaseFile))

}

func (s *EchoInputTestSuite) SetupTest() {

}

func (s *EchoInputTestSuite) TestSendInput() {

ctx := context.Background()

inputIndex, err := AddInput(ctx, LocalBlockchainHttpEndpoint, payload)
require.NoError(s.T(), err)

// Check input was correctly added to the blockchain
require.EqualValues(s.T(), 0, inputIndex)

require.NoError(s.T(), AdvanceTime(ctx, LocalBlockchainHttpEndpoint, 120))

// Get Input with vouchers and proofs
graphQlClient := graphql.NewClient("http://localhost:10000/graphql", nil)
getInputChan := make(chan *readerclient.Input, 1)
getInputErr := make(chan struct{}, 1)

go func() {
var resp *readerclient.Input
attempts := 0
for ; attempts < maxReadInputAttempts; attempts++ {
time.Sleep(2 * time.Second)
resp, err = readerclient.GetInput(ctx, graphQlClient, inputIndex)
if err == nil && resp.Status == "ACCEPTED" && resp.Vouchers != nil && resp.Vouchers[0].Proof != nil {
break
}
}
if attempts == maxReadInputAttempts {
getInputErr <- struct{}{}
return
}
getInputChan <- resp
}()

select {
case input := <-getInputChan:

//Check Input Fields

require.EqualValues(s.T(), 0, input.Index)
require.EqualValues(s.T(), "ACCEPTED", input.Status)
require.EqualValues(s.T(), payload, hexutil.Encode(input.Payload))

//Check Notices

require.NotNil(s.T(), input.Notices)
require.EqualValues(s.T(), 1, len(input.Notices))
require.EqualValues(s.T(), 0, input.Notices[0].Index)
require.EqualValues(s.T(), 0, input.Notices[0].InputIndex)
require.EqualValues(s.T(), payload, hexutil.Encode(input.Notices[0].Payload))

expectedProof, err := readExpectedProof("expected_notice_proof.json")
require.NoError(s.T(), err)

checkProof(s, expectedProof, input.Notices[0].Proof)

// Check Vouchers

require.NotNil(s.T(), input.Vouchers)
require.EqualValues(s.T(), 1, len(input.Vouchers))
require.EqualValues(s.T(), 0, input.Vouchers[0].Index)
require.EqualValues(s.T(), 0, input.Vouchers[0].InputIndex)
require.EqualValues(s.T(), "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", strings.ToLower(input.Vouchers[0].Destination.Hex()))
require.EqualValues(s.T(), payload, hexutil.Encode(input.Vouchers[0].Payload))

expectedProof, err = readExpectedProof("expected_voucher_proof.json")
require.NoError(s.T(), err)

checkProof(s, expectedProof, input.Vouchers[0].Proof)

// Check Reports

require.NotNil(s.T(), input.Reports)
require.EqualValues(s.T(), 1, len(input.Reports))
require.EqualValues(s.T(), 0, input.Reports[0].Index)
require.EqualValues(s.T(), 0, input.Reports[0].InputIndex)
require.EqualValues(s.T(), payload, hexutil.Encode(input.Reports[0].Payload))

break
case err = <-s.supervisorErr:
require.NoError(s.T(), err)
break
case <-getInputErr:
require.FailNow(s.T(), "Could not retrieve Voucher with Proof 0")
}

}

func readExpectedProof(proofFileName string) (*readerclient.Proof, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}

expectedProofJsonBytes, err := ioutil.ReadFile(filepath.Join(cwd, "resources", "echo_input", proofFileName))
if err != nil {
return nil, err
}

var proof readerclient.Proof
err = json.Unmarshal(expectedProofJsonBytes, &proof)
if err != nil {
return nil, err
}

return &proof, nil

}

func checkProof(s *EchoInputTestSuite, expectedProof *readerclient.Proof, proof *readerclient.Proof) {

// Machine state hashes are different between runs
proof.MachineStateHash = nil
expectedProof.MachineStateHash = nil

require.EqualValues(s.T(), expectedProof, proof)

}

func TestEchoInput(t *testing.T) {

suite.Run(t, new(EchoInputTestSuite))
}
Loading

0 comments on commit 1b71aa9

Please sign in to comment.