From ff274ca79238969ec94d23a0a5e61170f783f867 Mon Sep 17 00:00:00 2001 From: Karl Bartel Date: Thu, 19 Sep 2024 12:32:43 +0200 Subject: [PATCH] Stop at configured block (#2330) Adds a flag that fails validation for all blocks later than a specific block. This prevents nodes from accepting and producing blocks after that point. Also adds an e2e test that verifies the effectiveness of the stopping. Co-authored-by: alecps Co-authored-by: Piers Powlesland --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 8 ++ consensus/istanbul/backend/engine.go | 4 + core/forkid/forkid.go | 4 + e2e_test/e2e_bench_test.go | 4 +- e2e_test/e2e_test.go | 171 ++++++++++++++++++++++---- e2e_test/e2e_transfer_test.go | 4 +- eth/backend.go | 4 + eth/ethconfig/config.go | 3 + params/config.go | 7 ++ test/node.go | 176 +++++++++++++++++++++++---- 11 files changed, 340 insertions(+), 46 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index c841f3c5cb..531567eba6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -74,6 +74,7 @@ var ( utils.USBFlag, // utils.SmartCardDaemonPathFlag, utils.OverrideHForkFlag, + utils.L2MigrationBlockFlag, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, utils.TxPoolJournalFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 272b68d927..cd0a8a476f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -243,6 +243,11 @@ var ( Usage: "Manually specify the hfork block, overriding the bundled setting", } + L2MigrationBlockFlag = cli.Uint64Flag{ + Name: "l2migrationblock", + Usage: "Block number at which to halt the network for Celo L2 migration. This is the first block of Celo as an L2, and one after the last block of Celo as an L1. If unset or set to 0, no halt will occur.", + } + BloomFilterSizeFlag = cli.Uint64Flag{ Name: "bloomfilter.size", Usage: "Megabytes of memory allocated to bloom-filter for pruning", @@ -1723,6 +1728,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) godebug.SetGCPercent(int(gogc)) + if ctx.GlobalIsSet(L2MigrationBlockFlag.Name) { + cfg.L2MigrationBlock = new(big.Int).SetUint64(ctx.GlobalUint64(L2MigrationBlockFlag.Name)) + } if ctx.GlobalIsSet(SyncModeFlag.Name) { cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode) } diff --git a/consensus/istanbul/backend/engine.go b/consensus/istanbul/backend/engine.go index 6fb4999563..f75010ff5e 100644 --- a/consensus/istanbul/backend/engine.go +++ b/consensus/istanbul/backend/engine.go @@ -125,6 +125,10 @@ func (sb *Backend) verifyHeader(chain consensus.ChainHeaderReader, header *types if header.Number == nil { return errUnknownBlock } + if chain.Config().IsL2Migration(header.Number) { + sb.logger.Trace("Reject block after L2 migration", "num", header.Number) + return fmt.Errorf("Block number %d is after the L2 migration.", header.Number) + } // If the full chain isn't available (as on mobile devices), don't reject future blocks // This is due to potential clock skew diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 4f4628aca9..977adf80ec 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -224,6 +224,10 @@ func gatherForks(config *params.ChainConfig) []uint64 { if !strings.HasSuffix(field.Name, "Block") { continue } + // Do not include L2MigrationBlock in forkid as doing so will prevent syncing with nodes that have not set the L2MigrationBlock flag + if field.Name == "L2MigrationBlock" { + continue + } if field.Type != reflect.TypeOf(new(big.Int)) { continue } diff --git a/e2e_test/e2e_bench_test.go b/e2e_test/e2e_bench_test.go index 69b3bd4ba3..438d2f6e89 100644 --- a/e2e_test/e2e_bench_test.go +++ b/e2e_test/e2e_bench_test.go @@ -19,7 +19,7 @@ func BenchmarkNet100EmptyBlocks(b *testing.B) { for i := 0; i < b.N; i++ { ac := test.AccountConfig(n, 0) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(b, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(b, err) @@ -45,7 +45,7 @@ func BenchmarkNet1000Txs(b *testing.B) { ac := test.AccountConfig(n, n) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(b, err) accounts := test.Accounts(ac.DeveloperAccounts(), gc.ChainConfig()) network, shutdown, err := test.NewNetwork(ac, gc, ec) diff --git a/e2e_test/e2e_test.go b/e2e_test/e2e_test.go index fc73486ec6..0fb57c4aa6 100644 --- a/e2e_test/e2e_test.go +++ b/e2e_test/e2e_test.go @@ -15,6 +15,7 @@ import ( "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/common/hexutil" "github.com/celo-org/celo-blockchain/core/types" + "github.com/celo-org/celo-blockchain/eth/downloader" "github.com/celo-org/celo-blockchain/eth/tracers" "github.com/celo-org/celo-blockchain/log" "github.com/celo-org/celo-blockchain/mycelo/env" @@ -34,7 +35,7 @@ func init() { // This statement is commented out but left here since its very useful for // debugging problems and its non trivial to construct. // - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stdout, log.TerminalFormat(true)))) + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stdout, log.TerminalFormat(true)))) // This disables all logging which in general we want, because there is a lot log.Root().SetHandler(log.DiscardHandler()) @@ -45,7 +46,7 @@ func init() { func TestSendCelo(t *testing.T) { ac := test.AccountConfig(3, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -71,7 +72,7 @@ func TestSendCelo(t *testing.T) { func TestTraceSendCeloViaGoldToken(t *testing.T) { ac := test.AccountConfig(3, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -108,7 +109,7 @@ func TestTraceSendCeloViaGoldToken(t *testing.T) { func TestCallTraceTransactionNativeTransfer(t *testing.T) { ac := test.AccountConfig(1, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -146,7 +147,7 @@ func TestCallTraceTransactionNativeTransfer(t *testing.T) { func TestPrestateTransactionNativeTransfer(t *testing.T) { ac := test.AccountConfig(1, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -184,7 +185,7 @@ func TestSingleNodeNetworkManyTxs(t *testing.T) { txsPerIteration := 5 ac := test.AccountConfig(1, 1) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) gc.Istanbul.Epoch = uint64(iterations) * 50 // avoid the epoch for this test network, shutdown, err := test.NewNetwork(ac, gc, ec) @@ -210,7 +211,7 @@ func TestSingleNodeNetworkManyTxs(t *testing.T) { func TestEpochBlockMarshaling(t *testing.T) { accounts := test.AccountConfig(1, 0) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(accounts, gingerbreadBlock) + gc, ec, err := test.BuildConfig(accounts, gingerbreadBlock, nil) require.NoError(t, err) // Configure the shortest possible epoch, uptimeLookbackWindow minimum is 3 @@ -240,7 +241,7 @@ func TestEpochBlockMarshaling(t *testing.T) { func TestStartStopValidators(t *testing.T) { ac := test.AccountConfig(4, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, _, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -369,6 +370,134 @@ func TestStartStopValidators(t *testing.T) { } +func runStopNetworkAtL2BlockTest(ctx context.Context, t *testing.T, network test.Network, l2Block *big.Int) { + err := network.AwaitBlock(ctx, l2Block.Uint64()-1) + require.NoError(t, err) + + shortCtx, cancel := context.WithTimeout(ctx, time.Second*2) + defer cancel() + + // fail if any node adds a new block >= the migration block + var wg sync.WaitGroup + errorChan := make(chan error, len(network)) + + for _, n := range network { + wg.Add(1) + go func(n *test.Node) { + defer wg.Done() + err := n.Tracker.AwaitBlock(shortCtx, l2Block.Uint64()) + errorChan <- err + }(n) + } + + wg.Wait() + close(errorChan) + + // Collect and check errors + for err := range errorChan { + require.EqualError(t, err, context.DeadlineExceeded.Error()) + } +} + +func TestStopNetworkAtL2BlockSimple(t *testing.T) { + numValidators := 3 + numFullNodes := 2 + numFastNodes := 1 + ac := test.AccountConfig(numValidators, 2) + gingerbreadBlock := common.Big0 + l2BlockOG := big.NewInt(3) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, l2BlockOG) + require.NoError(t, err) + network, _, err := test.NewNetwork(ac, gc, ec) + require.NoError(t, err) + network, _, err = test.AddNonValidatorNodes(network, ec, uint64(numFullNodes), downloader.FullSync) + require.NoError(t, err) + network, shutdown, err := test.AddNonValidatorNodes(network, ec, uint64(numFastNodes), downloader.FastSync) + require.NoError(t, err) + + defer shutdown() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) + defer cancel() + + runStopNetworkAtL2BlockTest(ctx, t, network, l2BlockOG) +} + +func TestStopNetworkAtL2Block(t *testing.T) { + numValidators := 3 + numFullNodes := 2 + numFastNodes := 1 + ac := test.AccountConfig(numValidators, 2) + gingerbreadBlock := common.Big0 + l2BlockOG := big.NewInt(3) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, l2BlockOG) + require.NoError(t, err) + network, _, err := test.NewNetwork(ac, gc, ec) + require.NoError(t, err) + network, _, err = test.AddNonValidatorNodes(network, ec, uint64(numFullNodes), downloader.FullSync) + require.NoError(t, err) + network, shutdown, err := test.AddNonValidatorNodes(network, ec, uint64(numFastNodes), downloader.FastSync) + require.NoError(t, err) + + defer shutdown() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*400) + defer cancel() + + runStopNetworkAtL2BlockTest(ctx, t, network, l2BlockOG) + + shutdown() + + // Restart nodes with --l2-migration-block set to the next block + err = network.RestartNetworkWithMigrationBlockOffsets(l2BlockOG, []int64{1, 1, 1, 1, 1, 1}) + require.NoError(t, err) + + l2BlockPlusOne := new(big.Int).Add(l2BlockOG, big.NewInt(1)) + + runStopNetworkAtL2BlockTest(ctx, t, network, l2BlockPlusOne) + + shutdown() + + // Restart nodes with --l2-migration-block set to the same block + err = network.RestartNetworkWithMigrationBlockOffsets(l2BlockOG, []int64{1, 1, 1, 1, 1, 1}) + require.NoError(t, err) + + runStopNetworkAtL2BlockTest(ctx, t, network, l2BlockPlusOne) + + shutdown() + + // Restart nodes with different --l2-migration-block offsets + // If 2/3 validators (validators are the first 3 nodes in the network array) + // have the same migration block, the network should not be able to add any more blocks + err = network.RestartNetworkWithMigrationBlockOffsets(l2BlockOG, []int64{1, 1, 2, 2, 2, 2}) + require.NoError(t, err) + + runStopNetworkAtL2BlockTest(ctx, t, network, l2BlockPlusOne) + + shutdown() + + // Restart nodes with different --l2-migration-block offsets + // If 2/3 validators (validators are the first 3 nodes in the network array) + // have a greater migration block, the rest of the network should be able to add more blocks + err = network.RestartNetworkWithMigrationBlockOffsets(l2BlockOG, []int64{1, 2, 2, 2, 2, 2}) + require.NoError(t, err) + + l2BlockPlusTwo := new(big.Int).Add(l2BlockOG, big.NewInt(2)) + + runStopNetworkAtL2BlockTest(ctx, t, network[:1], l2BlockPlusOne) + runStopNetworkAtL2BlockTest(ctx, t, network[1:], l2BlockPlusTwo) + + shutdown() + + // Restart nodes with --l2-migration-block set to a prev block + err = network.RestartNetworkWithMigrationBlockOffsets(l2BlockOG, []int64{-1, -1, -1, -1, -1, -1}) + require.NoError(t, err) + + // The network should be unchanged + runStopNetworkAtL2BlockTest(ctx, t, network[:1], l2BlockPlusOne) + runStopNetworkAtL2BlockTest(ctx, t, network[1:], l2BlockPlusTwo) +} + // This test was created to reproduce the concurrent map access error in // https://github.com/celo-org/celo-blockchain/issues/1799 // @@ -377,7 +506,7 @@ func TestStartStopValidators(t *testing.T) { func TestBlockTracingConcurrentMapAccess(t *testing.T) { ac := test.AccountConfig(1, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -428,7 +557,7 @@ func TestBlockTracingConcurrentMapAccess(t *testing.T) { func TestBlockTracingSequentialAccess(t *testing.T) { ac := test.AccountConfig(1, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -463,7 +592,7 @@ type rpcCustomTransaction struct { func TestRPCDynamicTxGasPriceWithBigFeeCap(t *testing.T) { ac := test.AccountConfig(3, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -504,7 +633,7 @@ func TestRPCDynamicTxGasPriceWithBigFeeCap(t *testing.T) { func TestRPCDynamicTxGasPriceWithState(t *testing.T) { ac := test.AccountConfig(3, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) ec.TxLookupLimit = 0 ec.NoPruning = true @@ -582,7 +711,7 @@ func testRPCDynamicTxGasPriceWithoutState(t *testing.T, afterGingerbread, altern } cusdAddress := common.HexToAddress("0xd008") ac := test.AccountConfig(3, 2) - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) ec.TrieDirtyCache = 5 require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) @@ -656,7 +785,7 @@ func pruneStateOfBlock(ctx context.Context, node *test.Node, blockNumber *big.In func runMochaTest(t *testing.T, add_args func(*env.AccountsConfig, *genesis.Config, test.Network) []string) { ac := test.AccountConfig(1, 1) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -702,7 +831,7 @@ func TestEthersJSCompatibility(t *testing.T) { func TestEthersJSCompatibilityDisableAfterGingerbread(t *testing.T) { ac := test.AccountConfig(1, 1) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) // Check fields present (compatibility set by default) @@ -750,7 +879,7 @@ func TestEthersJSCompatibilityDisableAfterGingerbread(t *testing.T) { func TestEthersJSCompatibilityDisableBeforeGingerbread(t *testing.T) { ac := test.AccountConfig(1, 1) var gingerbreadBlock *big.Int = nil - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) // Check fields present (compatibility set by default) @@ -807,7 +936,7 @@ func TestEthCompatibilityFieldsOnGenesisBlock(t *testing.T) { var gingerbreadBlock *big.Int = nil // Fist we test without eth compatibility to ensure that the setting has an effect. - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) ec.RPCEthCompatibility = false require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) @@ -826,7 +955,7 @@ func TestEthCompatibilityFieldsOnGenesisBlock(t *testing.T) { // Now we with eth compatility enabled and see that gasLimit and baseFee // are returned on the block. - gc, ec, err = test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err = test.BuildConfig(ac, gingerbreadBlock, nil) ec.RPCEthCompatibility = true require.NoError(t, err) network, shutdown, err = test.NewNetwork(ac, gc, ec) @@ -848,7 +977,7 @@ func TestSettingGingerbreadOnGenesisBlock(t *testing.T) { // Fist we test without gingerbread to ensure that setting the gingerbread // actually has an effect. var gingerbreadBlock *big.Int = nil - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) ec.RPCEthCompatibility = false require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) @@ -867,7 +996,7 @@ func TestSettingGingerbreadOnGenesisBlock(t *testing.T) { // Now we check that setting the gingerbread block at genesis causes gasLimit and baseFee to be set on the block. gingerbreadBlock = big.NewInt(0) - gc, ec, err = test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err = test.BuildConfig(ac, gingerbreadBlock, nil) ec.RPCEthCompatibility = false require.NoError(t, err) network, shutdown, err = test.NewNetwork(ac, gc, ec) @@ -886,7 +1015,7 @@ func TestSettingGingerbreadOnGenesisBlock(t *testing.T) { func TestGetFinalizedBlock(t *testing.T) { ac := test.AccountConfig(2, 2) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) diff --git a/e2e_test/e2e_transfer_test.go b/e2e_test/e2e_transfer_test.go index 4ebbbf929f..cdc2f0cdba 100644 --- a/e2e_test/e2e_transfer_test.go +++ b/e2e_test/e2e_transfer_test.go @@ -37,7 +37,7 @@ const ( func TestTransferCELO(t *testing.T) { ac := test.AccountConfig(1, 3) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -259,7 +259,7 @@ func prepareTransaction(txArgs ethapi.TransactionArgs, senderKey *ecdsa.PrivateK func TestTransferERC20(t *testing.T) { ac := test.AccountConfig(1, 3) gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, gingerbreadBlock, nil) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) diff --git a/eth/backend.go b/eth/backend.go index 0aea69f1e6..4d6f1e7eb0 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -138,6 +138,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { log.Info("Initialised chain configuration", "config", chainConfig) chainConfig.FullHeaderChainAvailable = config.SyncMode.SyncFullHeaderChain() + if config.L2MigrationBlock != nil { + chainConfig.L2MigrationBlock = config.L2MigrationBlock + } + if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { log.Error("Failed to recover state", "error", err) } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 264eaf7487..e1431e964a 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -165,6 +165,9 @@ type Config struct { // HFork block override (TODO: remove after the fork) OverrideHFork *big.Int `toml:",omitempty"` + // l2 migration block, last block of l1 before l2 migration + L2MigrationBlock *big.Int `toml:",omitempty"` + // The minimum required peers in order for syncing to be initiated, if left // at 0 then the default will be used. MinSyncPeers int `toml:",omitempty"` diff --git a/params/config.go b/params/config.go index 419cb02bd0..e012344f8d 100644 --- a/params/config.go +++ b/params/config.go @@ -313,6 +313,7 @@ type ChainConfig struct { GingerbreadBlock *big.Int `json:"gingerbreadBlock,omitempty"` // Gingerbread switch block (nil = no fork, 0 = already activated) GingerbreadP2Block *big.Int `json:"gingerbreadP2Block,omitempty"` // GingerbreadP2 switch block (nil = no fork, 0 = already activated) HForkBlock *big.Int `json:"hforkBlock,omitempty"` // HFork switch block (nil = no fork, 0 = already activated) + L2MigrationBlock *big.Int `json:"l2MigrationBlock,omitempty"` // l2 migration block / first block of Celo as L2 / 1 + last block of Celo as L1 (nil = no migration, 0 = no migration) Istanbul *IstanbulConfig `json:"istanbul,omitempty"` // This does not belong here but passing it to every function is not possible since that breaks @@ -464,6 +465,11 @@ func (c *ChainConfig) IsGingerbreadP2(num *big.Int) bool { return isForked(c.GingerbreadP2Block, num) } +// IsL2Migration returns whether num represents a block number greater than or equal to the L2 migration block (fist block of Celo as L2 / 1 + last block of Celo as L1) +func (c *ChainConfig) IsL2Migration(num *big.Int) bool { + return isForked(c.L2MigrationBlock, num) && c.L2MigrationBlock.Cmp(big.NewInt(0)) > 0 // return false if L2MigrationBlock is nil or 0 +} + func (c *ChainConfig) IsHFork(num *big.Int) bool { return isForked(c.HForkBlock, num) } @@ -734,6 +740,7 @@ func (c *ChainConfig) DeepCopy() *ChainConfig { GingerbreadBlock: copyBigIntOrNil(c.GingerbreadBlock), GingerbreadP2Block: copyBigIntOrNil(c.GingerbreadP2Block), HForkBlock: copyBigIntOrNil(c.HForkBlock), + L2MigrationBlock: copyBigIntOrNil(c.L2MigrationBlock), Istanbul: &IstanbulConfig{ Epoch: c.Istanbul.Epoch, diff --git a/test/node.go b/test/node.go index 74e3f9b879..4b189a4983 100644 --- a/test/node.go +++ b/test/node.go @@ -15,6 +15,7 @@ import ( ethereum "github.com/celo-org/celo-blockchain" "github.com/celo-org/celo-blockchain/eth/ethconfig" + "github.com/celo-org/celo-blockchain/log" "github.com/celo-org/celo-blockchain/common" "github.com/celo-org/celo-blockchain/consensus/istanbul" @@ -60,7 +61,7 @@ var ( } BaseEthConfig = ð.Config{ - SyncMode: downloader.FullSync, + SyncMode: downloader.FullSync, // TODO(Alec) do we need to test different sync modes? MinSyncPeers: 1, DatabaseCache: 256, DatabaseHandles: 256, @@ -110,7 +111,60 @@ type Node struct { SentTxs []*types.Transaction } -// NewNode creates a new running node with the provided config. +// NewNonValidatorNode creates a new running non-validator node with the provided config. +func NewNonValidatorNode( + nc *node.Config, + ec *eth.Config, + genesis *core.Genesis, + syncmode downloader.SyncMode, +) (*Node, error) { + + // Copy the node config so we can modify it without damaging the original + ncCopy := *nc + + // p2p key and address, this is not the same as the validator key. + p2pKey, err := crypto.GenerateKey() + if err != nil { + return nil, err + } + ncCopy.P2P.PrivateKey = p2pKey + + // Make temp datadir + datadir, err := ioutil.TempDir("", "celo_datadir") + if err != nil { + return nil, err + } + ncCopy.DataDir = datadir + + // copy the base eth config, so we can modify it without damaging the + // original. + ecCopy := ð.Config{} + err = copyObject(ec, ecCopy) + if err != nil { + return nil, err + } + ecCopy.Genesis = genesis + ecCopy.NetworkId = genesis.Config.ChainID.Uint64() + ecCopy.Istanbul.Validator = false + ecCopy.SyncMode = syncmode + + // We set these values here to avoid a panic in the eth service when these values are unset during fast sync + if syncmode == downloader.FastSync { + ecCopy.TrieCleanCache = 5 + ecCopy.TrieDirtyCache = 5 + ecCopy.SnapshotCache = 5 + } + + node := &Node{ + Config: &ncCopy, + EthConfig: ecCopy, + Tracker: NewTracker(), + } + + return node, node.Start() +} + +// NewNode creates a new running validator node with the provided config. func NewNode( validatorAccount *env.Account, nc *node.Config, @@ -201,21 +255,23 @@ func (n *Node) Start() error { // The ListenAddr is set at p2p server startup, save it here. n.P2PListenAddr = n.Node.Server().ListenAddr - // Import the node key into the keystore and then unlock it, the keystore - // is the interface used for signing operations so the node key needs to be - // inside it. - ks, _, err := n.Config.GetKeyStore() - if err != nil { - return err - } - n.AccountManager().AddBackend(ks) - account, err := ks.ImportECDSA(n.Key, "") - if err != nil { - return err - } - err = ks.TimedUnlock(account, "", 0) - if err != nil { - return err + if n.EthConfig.Istanbul.Validator { + // Import the node key into the keystore and then unlock it, the keystore + // is the interface used for signing operations so the node key needs to be + // inside it. + ks, _, err := n.Config.GetKeyStore() + if err != nil { + return err + } + n.AccountManager().AddBackend(ks) + account, err := ks.ImportECDSA(n.Key, "") + if err != nil { + return err + } + err = ks.TimedUnlock(account, "", 0) + if err != nil { + return err + } } _, _, err = core.SetupGenesisBlock(n.Eth.ChainDb(), n.EthConfig.Genesis) @@ -230,9 +286,12 @@ func (n *Node) Start() error { if err != nil { return err } - err = n.Eth.StartMining() - if err != nil { - return err + + if n.EthConfig.Istanbul.Validator { + err = n.Eth.StartMining() + if err != nil { + return err + } } // Note we need to use the LocalNode from the p2p server because that is @@ -344,7 +403,7 @@ func AccountConfig(numValidators, numExternal int) *env.AccountsConfig { // NOTE: Do not edit the Istanbul field of the returned genesis config it will // be overwritten with the corresponding config from the Istanbul field of the // returned eth config. -func BuildConfig(accounts *env.AccountsConfig, gingerbreadBlock *big.Int) (*genesis.Config, *ethconfig.Config, error) { +func BuildConfig(accounts *env.AccountsConfig, gingerbreadBlock, l2MigrationBlock *big.Int) (*genesis.Config, *ethconfig.Config, error) { gc, err := genesis.CreateCommonGenesisConfig( big.NewInt(1), accounts.AdminAccount().Address, @@ -361,6 +420,7 @@ func BuildConfig(accounts *env.AccountsConfig, gingerbreadBlock *big.Int) (*gene // original. ec := ð.Config{} err = copyObject(BaseEthConfig, ec) + ec.L2MigrationBlock = l2MigrationBlock return gc, ec, err } @@ -440,6 +500,47 @@ func NewNetwork(accounts *env.AccountsConfig, gc *genesis.Config, ec *eth.Config } shutdown := func() { + log.Info("Shutting down network from test") + for _, err := range network.Shutdown() { + fmt.Println(err.Error()) + } + } + + return network, shutdown, nil +} + +// AddNonValidatorNodes Adds non-validator nodes to the network with the specified sync mode. +func AddNonValidatorNodes(network Network, ec *eth.Config, numNodes uint64, syncmode downloader.SyncMode) (Network, func(), error) { + var nodes Network = make([]*Node, numNodes) + genesis := network[0].EthConfig.Genesis + + for i := uint64(0); i < numNodes; i++ { + n, err := NewNonValidatorNode(baseNodeConfig, ec, genesis, syncmode) + if err != nil { + return nil, nil, fmt.Errorf("failed to build full node for network: %v", err) + } + nodes[i] = n + } + + // Connect nodes to each other + for i := range nodes { + nodes[i].AddPeers(network...) + nodes[i].AddPeers(nodes[i+1:]...) + } + + network = append(network, nodes...) + + // Give nodes some time to connect. Also there is a race condition in + // miner.worker its field snapshotBlock is set only when new transactions + // are received or commitNewWork is called. But both of these happen in + // goroutines separate to the call to miner.Start and miner.Start does not + // wait for snapshotBlock to be set. Therefore there is currently no way to + // know when it is safe to call estimate gas. What we do here is sleep a + // bit and cross our fingers. + time.Sleep(25 * time.Millisecond) + + shutdown := func() { + log.Info("Shutting down network from test") for _, err := range network.Shutdown() { fmt.Println(err.Error()) } @@ -485,6 +586,39 @@ func (n Network) Shutdown() []error { return errors } +func (n Network) RestartNetworkWithMigrationBlockOffsets(l2MigrationBlockOG *big.Int, offsets []int64) error { + if len(offsets) != len(n) { + return fmt.Errorf("number of l2BlockMigration offsets must match number of nodes") + } + + errors := []error{} + for i, node := range n { + node.EthConfig.L2MigrationBlock = new(big.Int).Add(l2MigrationBlockOG, big.NewInt(offsets[i])) + err := node.Start() + if err != nil { + errors = append(errors, err) + } + } + if len(errors) > 0 { + return fmt.Errorf("failed to restart network: %v", errors) + } + + for i, node := range n { + node.AddPeers(n[:i]...) + } + + // We need to wait here to allow the call to "Backend.RefreshValPeers" to + // complete before adding peers. This is because "Backend.RefreshValPeers" + // deletes all peers and then re-adds any peers from the cached + // connections, but in the case that peers were recently added there may + // not have been enough time to connect to them and populate the connection + // cache, and in that case "Backend.RefreshValPeers" simply removes all the + // peers. + time.Sleep(25 * time.Millisecond) + + return nil +} + // Uses the client to suggest a gas price and to estimate the gas. func BuildSignedTransaction( client *ethclient.Client,