From 7279b444b5d39346f765bfc04a2b4655e34cd339 Mon Sep 17 00:00:00 2001 From: Herbert Jordan Date: Thu, 14 Nov 2024 12:49:40 +0100 Subject: [PATCH] Add an integration test covering the restart of a node --- gossip/gasprice/gasprice.go | 4 +++ gossip/service.go | 9 ++--- tests/integration_test_net.go | 57 +++++++++++++++++++---------- tests/integration_test_net_test.go | 7 ++-- tests/node_restart_test.go | 58 ++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 tests/node_restart_test.go diff --git a/gossip/gasprice/gasprice.go b/gossip/gasprice/gasprice.go index a26cfcd7f..1ea54e6ca 100644 --- a/gossip/gasprice/gasprice.go +++ b/gossip/gasprice/gasprice.go @@ -119,6 +119,10 @@ func NewOracle(params Config, backend Reader) *Oracle { } } +func (gpo *Oracle) SetReader(backend Reader) { + gpo.backend = backend +} + func (gpo *Oracle) Start() { gpo.wg.Add(1) go func() { diff --git a/gossip/service.go b/gossip/service.go index 0358d6d46..1109efaba 100644 --- a/gossip/service.go +++ b/gossip/service.go @@ -217,16 +217,17 @@ func newService(config Config, store *Store, blockProc BlockProc, engine lachesi svc.gasPowerCheckReader.Ctx.Store(NewGasPowerContext(svc.store, svc.store.GetValidators(), svc.store.GetEpoch(), net.Economy)) // read gaspower check data from DB svc.checkers = makeCheckers(config.HeavyCheck, txSigner, &svc.heavyCheckReader, &svc.gasPowerCheckReader, svc.store) + // create GPO + svc.gpo = gasprice.NewOracle(svc.config.GPO, nil) + // create tx pool stateReader := &EvmStateReader{ ServiceFeed: &svc.feed, store: svc.store, + gpo: svc.gpo, } svc.txpool = newTxPool(stateReader) - - // create GPO - svc.gpo = gasprice.NewOracle(svc.config.GPO, &GPOBackend{svc.store, svc.txpool}) - stateReader.gpo = svc.gpo + svc.gpo.SetReader(&GPOBackend{svc.store, svc.txpool}) // init dialCandidates dnsclient := dnsdisc.NewClient(dnsdisc.Config{}) diff --git a/tests/integration_test_net.go b/tests/integration_test_net.go index 8a0e1faca..7445a42ab 100644 --- a/tests/integration_test_net.go +++ b/tests/integration_test_net.go @@ -44,6 +44,7 @@ import ( // integration test networks can also be used for automated integration and // regression tests for client code. type IntegrationTestNet struct { + directory string done <-chan struct{} validator Account } @@ -53,23 +54,39 @@ type IntegrationTestNet struct { // is intended to facilitate debugging of client code in the context of a running // node. func StartIntegrationTestNet(directory string) (*IntegrationTestNet, error) { + + // initialize the data directory for the single node on the test network + // equivalent to running `sonictool --datadir genesis fake 1` + originalArgs := os.Args + os.Args = []string{"sonictool", "--datadir", directory, "genesis", "fake", "1"} + sonictool.Run() + os.Args = originalArgs + + // start the fakenet sonic node + result := &IntegrationTestNet{ + directory: directory, + validator: Account{evmcore.FakeKey(1)}, + } + + if err := result.start(); err != nil { + return nil, fmt.Errorf("failed to start the test network: %w", err) + } + return result, nil +} + +func (n *IntegrationTestNet) start() error { + if n.done != nil { + return errors.New("network already started") + } done := make(chan struct{}) go func() { defer close(done) - originalArgs := os.Args - defer func() { os.Args = originalArgs }() - - // initialize the data directory for the single node on the test network - // equivalent to running `sonictool --datadir genesis fake 1` - os.Args = []string{"sonictool", "--datadir", directory, "genesis", "fake", "1"} - sonictool.Run() - // start the fakenet sonic node // equivalent to running `sonicd ...` but in this local process os.Args = []string{ "sonicd", - "--datadir", directory, + "--datadir", n.directory, "--fakenet", "1/1", "--http", "--http.addr", "0.0.0.0", "--http.port", "18545", "--http.api", "admin,eth,web3,net,txpool,ftm,trace,debug", @@ -79,19 +96,16 @@ func StartIntegrationTestNet(directory string) (*IntegrationTestNet, error) { sonicd.Run() }() - result := &IntegrationTestNet{ - done: done, - validator: Account{evmcore.FakeKey(1)}, - } + n.done = done // connect to blockchain network - client, err := result.GetClient() + client, err := n.GetClient() if err != nil { - return nil, fmt.Errorf("failed to connect to the Ethereum client: %w", err) + return fmt.Errorf("failed to connect to the Ethereum client: %w", err) } defer client.Close() - const timeout = 30 * time.Second + const timeout = 300 * time.Second start := time.Now() // wait for the node to be ready to serve requests @@ -107,16 +121,23 @@ func StartIntegrationTestNet(directory string) (*IntegrationTestNet, error) { } continue } - return result, nil + return nil } - return nil, fmt.Errorf("failed to successfully start up a test network within %d", timeout) + return fmt.Errorf("failed to successfully start up a test network within %v", timeout) } // Stop shuts the underlying network down. func (n *IntegrationTestNet) Stop() { syscall.Kill(syscall.Getpid(), syscall.SIGINT) <-n.done + n.done = nil +} + +// Stops and restarts the single node on the test network. +func (n *IntegrationTestNet) Restart() error { + n.Stop() + return n.start() } // EndowAccount sends a requested amount of tokens to the given account. This is diff --git a/tests/integration_test_net_test.go b/tests/integration_test_net_test.go index 21e6be2ca..9667cb395 100644 --- a/tests/integration_test_net_test.go +++ b/tests/integration_test_net_test.go @@ -10,11 +10,14 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -func TestIntegrationTestNet_CanStartAndStopIntegrationTestNet(t *testing.T) { +func TestIntegrationTestNet_CanStartRestartAndStopIntegrationTestNet(t *testing.T) { dataDir := t.TempDir() net, err := StartIntegrationTestNet(dataDir) if err != nil { - t.Fatalf("Failed to start the fake network: %v", err) + t.Fatalf("Failed to start the test network: %v", err) + } + if err := net.Restart(); err != nil { + t.Fatalf("Failed to restart the test network: %v", err) } net.Stop() } diff --git a/tests/node_restart_test.go b/tests/node_restart_test.go new file mode 100644 index 000000000..018b78497 --- /dev/null +++ b/tests/node_restart_test.go @@ -0,0 +1,58 @@ +package tests + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestNodeRestart_CanRestartAndRestoreItsState(t *testing.T) { + const numBlocks = 3 + const numRestarts = 2 + require := require.New(t) + + net, err := StartIntegrationTestNet(t.TempDir()) + require.NoError(err) + defer net.Stop() + + // All transaction hashes indexed by their blocks. + receipts := map[int]types.Receipts{} + + // Run through multiple restarts. + for i := 0; i < numRestarts; i++ { + for range numBlocks { + receipt, err := net.EndowAccount(common.Address{42}, 100) + if err != nil { + t.Fatalf("failed to endow account; %v", err) + } + block := int(receipt.BlockNumber.Int64()) + receipts[block] = append(receipts[block], receipt) + } + require.NoError(net.Restart()) + } + + // Check that access to all blocks is possible. + client, err := net.GetClient() + require.NoError(err) + defer client.Close() + + lastBlock, err := client.BlockByNumber(context.Background(), nil) + require.NoError(err) + require.GreaterOrEqual(lastBlock.NumberU64(), uint64(numBlocks*numRestarts)) + + for i := range lastBlock.NumberU64() { + block, err := client.BlockByNumber(context.Background(), big.NewInt(int64(i))) + require.NoError(err) + + for _, receipt := range receipts[int(i)] { + position := receipt.TransactionIndex + require.Less(int(position), len(block.Transactions()), "block %d", i) + got := block.Transactions()[position].Hash() + require.Equal(got, receipt.TxHash, "block %d, tx %d", i, position) + } + } +}