// Copyright (C) 2019-2025, Lux Partners Limited. All rights reserved. // See the file LICENSE for licensing terms. package core import ( "fmt" "math/big" "testing" "github.com/luxfi/evm/consensus/dummy" "github.com/luxfi/evm/core/rawdb" "github.com/luxfi/evm/core/state" "github.com/luxfi/evm/core/types" "github.com/luxfi/evm/params" "github.com/luxfi/evm/upgrade/ap4" "github.com/luxfi/geth/common" "github.com/luxfi/geth/crypto" "github.com/luxfi/geth/ethdb" ethparams "github.com/luxfi/geth/params" "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) // TODO: Add proper callback support when consensus engine supports it // For now, these tests will use the default dummy engine type createFunc func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, dataPath string) (*BlockChain, error) type ExtChainTest struct { Name string testFunc func( t *testing.T, create createFunc, ) } var extTests = []ExtChainTest{ { "InsertChainAcceptSingleBlock", InsertChainAcceptSingleBlockTest, }, { "InsertForkedChain", InsertLongForkedChainTest, }, { "AcceptNonCanonicalBlock", AcceptNonCanonicalBlockTest, }, { "SetPreferenceRewind", SetPreferenceRewindTest, }, { "BuildOnVariousStages", BuildOnVariousStagesTest, }, { "EmptyBlocks", EmptyBlocksTest, }, { "EmptyAndNonEmptyBlocks", EmptyAndNonEmptyBlocksTest, }, { "ReorgReInsert", ReorgReInsertTest, }, { "AcceptBlockIdenticalStateRoot", AcceptBlockIdenticalStateRootTest, }, { "ReprocessAcceptBlockIdenticalStateRoot", ReprocessAcceptBlockIdenticalStateRootTest, }, { "GenerateChainInvalidBlockFee", GenerateChainInvalidBlockFeeTest, }, { "InsertChainInvalidBlockFee", InsertChainInvalidBlockFeeTest, }, { "InsertChainValidBlockFee", InsertChainValidBlockFeeTest, }, } type ReexecTestFunc func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, dataPath string, commitInterval uint64) (*BlockChain, error) type ReexecTest struct { Name string testFunc func( t *testing.T, create ReexecTestFunc, ) } var reexecTests = []ReexecTest{ { "ReexecBlocks", ReexecBlocksTest, }, { "ReexecMaxBlocks", ReexecMaxBlocksTest, }, } func copyMemDBExt(db ethdb.Database) (ethdb.Database, error) { newDB := rawdb.NewMemoryDatabase() iter := db.NewIterator(nil, nil) defer iter.Release() for iter.Next() { if err := newDB.Put(iter.Key(), iter.Value()); err != nil { return nil, err } } return newDB, nil } // checkBlockChainState creates a new BlockChain instance and checks that exporting each block from // genesis to last accepted from the original instance yields the same last accepted block and state // root. // Additionally, create another BlockChain instance from [originalDB] to ensure that BlockChain is // persisted correctly through a restart. func checkBlockChainStateExt( t *testing.T, bc *BlockChain, gspec *Genesis, originalDB ethdb.Database, create createFunc, checkState func(sdb *state.StateDB) error, ) (*BlockChain, *BlockChain, *BlockChain) { var ( lastAcceptedBlock = bc.LastConsensusAcceptedBlock() newDB = rawdb.NewMemoryDatabase() ) acceptedState, err := bc.StateAt(lastAcceptedBlock.Root()) if err != nil { t.Fatal(err) } if err := checkState(acceptedState); err != nil { t.Fatalf("Check state failed for original blockchain due to: %s", err) } // Get ChainDataDir from the chain config's LuxContext oldChainDataDir := "" if cfg, ok := bc.Config().(*params.ChainConfig); ok && cfg.ConsensusCtx != nil { oldChainDataDir = cfg.ConsensusCtx.ChainDataDir } newBlockChain, err := create(newDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatalf("Failed to create new blockchain instance: %s", err) } defer newBlockChain.Stop() for i := uint64(1); i <= lastAcceptedBlock.NumberU64(); i++ { block := bc.GetBlockByNumber(i) if block == nil { t.Fatalf("Failed to retrieve block by number %d from original chain", i) } if err := newBlockChain.InsertBlock(block); err != nil { t.Fatalf("Failed to insert block %s:%d due to %s", block.Hash().Hex(), block.NumberU64(), err) } if err := newBlockChain.Accept(block); err != nil { t.Fatalf("Failed to accept block %s:%d due to %s", block.Hash().Hex(), block.NumberU64(), err) } } newBlockChain.DrainAcceptorQueue() newLastAcceptedBlock := newBlockChain.LastConsensusAcceptedBlock() if newLastAcceptedBlock.Hash() != lastAcceptedBlock.Hash() { t.Fatalf("Expected new blockchain to have last accepted block %s:%d, but found %s:%d", lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64(), newLastAcceptedBlock.Hash().Hex(), newLastAcceptedBlock.NumberU64()) } // Check that the state of [newBlockChain] passes the check acceptedState, err = newBlockChain.StateAt(lastAcceptedBlock.Root()) if err != nil { t.Fatal(err) } if err := checkState(acceptedState); err != nil { t.Fatalf("Check state failed for newly generated blockchain due to: %s", err) } // Copy the database over to prevent any issues when re-using [originalDB] after this call. originalDB, err = copyMemDBExt(originalDB) if err != nil { t.Fatal(err) } restartedChain, err := create(originalDB, gspec, lastAcceptedBlock.Hash(), oldChainDataDir) if err != nil { t.Fatal(err) } defer restartedChain.Stop() if currentBlock := restartedChain.CurrentBlock(); currentBlock.Hash() != lastAcceptedBlock.Hash() { t.Fatalf("Expected restarted chain to have current block %s:%d, but found %s:%d", lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } if restartedLastAcceptedBlock := restartedChain.LastConsensusAcceptedBlock(); restartedLastAcceptedBlock.Hash() != lastAcceptedBlock.Hash() { t.Fatalf("Expected restarted chain to have current block %s:%d, but found %s:%d", lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64(), restartedLastAcceptedBlock.Hash().Hex(), restartedLastAcceptedBlock.NumberU64()) } // Check that the state of [restartedChain] passes the check acceptedState, err = restartedChain.StateAt(lastAcceptedBlock.Root()) if err != nil { t.Fatal(err) } if err := checkState(acceptedState); err != nil { t.Fatalf("Check state failed for restarted blockchain due to: %s", err) } return bc, newBlockChain, restartedChain } func InsertChainAcceptSingleBlockTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() // This call generates a chain of 3 blocks. signer := types.HomesteadSigner{} _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Insert three blocks into the chain and accept only the first block. if _, err := blockchain.InsertChain(chain); err != nil { t.Fatal(err) } if err := blockchain.Accept(chain[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce := sdb.GetNonce(addr1) if nonce != 1 { return fmt.Errorf("expected nonce addr1: 1, found nonce: %d", nonce) } transferredFunds := uint256.MustFromBig(big.NewInt(10000)) balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) expectedBalance2 := transferredFunds if balance2.Cmp(expectedBalance2) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) } nonce = sdb.GetNonce(addr2) if nonce != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func InsertLongForkedChainTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() numBlocks := 129 signer := types.HomesteadSigner{} _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Generate the forked chain to be longer than the original chain to check for a regression where // a longer chain can trigger a reorg. _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks+1, 10, func(i int, gen *BlockGen) { // Generate a transaction with a different amount to ensure [chain2] is different than [chain1]. tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(5000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } if blockchain.snaps != nil { if want, got := 1, blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } // Insert both chains. if _, err := blockchain.InsertChain(chain1); err != nil { t.Fatal(err) } if blockchain.snaps != nil { if want, got := 1+len(chain1), blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } if _, err := blockchain.InsertChain(chain2); err != nil { t.Fatal(err) } if blockchain.snaps != nil { if want, got := 1+len(chain1)+len(chain2), blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } currentBlock := blockchain.CurrentBlock() expectedCurrentBlock := chain1[len(chain1)-1] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } // Accept the first block in [chain1], reject all blocks in [chain2] to // mimic the order that the consensus engine will call Accept/Reject in // and then Accept the rest of the blocks in [chain1]. if err := blockchain.Accept(chain1[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() if blockchain.snaps != nil { // Snap layer count should be 1 fewer if want, got := len(chain1)+len(chain2), blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } for i := 0; i < len(chain2); i++ { if err := blockchain.Reject(chain2[i]); err != nil { t.Fatal(err) } if blockchain.snaps != nil { // Snap layer count should decrease by 1 per Reject if want, got := len(chain1)+len(chain2)-i-1, blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } } if blockchain.snaps != nil { if want, got := len(chain1), blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } for i := 1; i < len(chain1); i++ { if err := blockchain.Accept(chain1[i]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() if blockchain.snaps != nil { // Snap layer count should decrease by 1 per Accept if want, got := len(chain1)-i, blockchain.snaps.NumBlockLayers(); got != want { t.Fatalf("incorrect snapshot layer count; got %d, want %d", got, want) } } } lastAcceptedBlock := blockchain.LastConsensusAcceptedBlock() expectedLastAcceptedBlock := chain1[len(chain1)-1] if lastAcceptedBlock.Hash() != expectedLastAcceptedBlock.Hash() { t.Fatalf("Expected last accepted block to be %s:%d, but found %s%d", expectedLastAcceptedBlock.Hash().Hex(), expectedLastAcceptedBlock.NumberU64(), lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 129 { return fmt.Errorf("expected addr1 nonce: 129, found nonce %d", nonce1) } balance1 := sdb.GetBalance(addr1) transferredFunds := uint256.NewInt(129 * 10_000) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce2) } balance2 := sdb.GetBalance(addr2) if balance2.Cmp(transferredFunds) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", transferredFunds, balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func AcceptNonCanonicalBlockTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) // We use two separate databases since GenerateChain commits the state roots to its underlying // database. chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() numBlocks := 3 signer := types.HomesteadSigner{} _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction with a different amount to create a chain of blocks different from [chain1] tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(5000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Insert three blocks into the chain and accept only the first. if _, err := blockchain.InsertChain(chain1); err != nil { t.Fatal(err) } if _, err := blockchain.InsertChain(chain2); err != nil { t.Fatal(err) } currentBlock := blockchain.CurrentBlock() expectedCurrentBlock := chain1[len(chain1)-1] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } // Accept the first block in [chain2], reject all blocks in [chain1] to // mimic the order that the consensus engine will call Accept/Reject in. if err := blockchain.Accept(chain2[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() for i := 0; i < len(chain1); i++ { if err := blockchain.Reject(chain1[i]); err != nil { t.Fatal(err) } require.False(t, blockchain.HasBlock(chain1[i].Hash(), chain1[i].NumberU64())) } lastAcceptedBlock := blockchain.LastConsensusAcceptedBlock() expectedLastAcceptedBlock := chain2[0] if lastAcceptedBlock.Hash() != expectedLastAcceptedBlock.Hash() { t.Fatalf("Expected last accepted block to be %s:%d, but found %s%d", expectedLastAcceptedBlock.Hash().Hex(), expectedLastAcceptedBlock.NumberU64(), lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 1 { return fmt.Errorf("expected addr1 nonce: 1, found nonce: %d", nonce1) } balance1 := sdb.GetBalance(addr1) transferredFunds := uint256.NewInt(5000) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance) != 0 { return fmt.Errorf("expected balance1: %d, found balance: %d", expectedBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce %d", nonce2) } balance2 := sdb.GetBalance(addr2) if balance2.Cmp(transferredFunds) != 0 { return fmt.Errorf("expected balance2: %d, found %d", transferredFunds, balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func SetPreferenceRewindTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() numBlocks := 3 signer := types.HomesteadSigner{} _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Insert three blocks into the chain and accept only the first. if _, err := blockchain.InsertChain(chain); err != nil { t.Fatal(err) } currentBlock := blockchain.CurrentBlock() expectedCurrentBlock := chain[len(chain)-1] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } // SetPreference to an ancestor of the currently preferred block. Test that this unlikely, but possible behavior // is handled correctly. if err := blockchain.SetPreference(chain[0]); err != nil { t.Fatal(err) } currentBlock = blockchain.CurrentBlock() expectedCurrentBlock = chain[0] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } lastAcceptedBlock := blockchain.LastConsensusAcceptedBlock() expectedLastAcceptedBlock := blockchain.Genesis() if lastAcceptedBlock.Hash() != expectedLastAcceptedBlock.Hash() { t.Fatalf("Expected last accepted block to be %s:%d, but found %s%d", expectedLastAcceptedBlock.Hash().Hex(), expectedLastAcceptedBlock.NumberU64(), lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } // check the state of the last accepted block checkGenesisState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 0 { return fmt.Errorf("expected addr1 nonce: 0, found nonce: %d", nonce1) } balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) if balance1.Cmp(genesisBalance) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", genesisBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce2) } balance2 := sdb.GetBalance(addr2) if balance2.Cmp(common.U2560) != 0 { return fmt.Errorf("expected addr2 balance: 0, found balance %d", balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkGenesisState) if err := blockchain.Accept(chain[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() lastAcceptedBlock = blockchain.LastConsensusAcceptedBlock() expectedLastAcceptedBlock = chain[0] if lastAcceptedBlock.Hash() != expectedLastAcceptedBlock.Hash() { t.Fatalf("Expected last accepted block to be %s:%d, but found %s%d", expectedLastAcceptedBlock.Hash().Hex(), expectedLastAcceptedBlock.NumberU64(), lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64()) } if err := blockchain.ValidateCanonicalChain(); err != nil { t.Fatal(err) } checkUpdatedState := func(sdb *state.StateDB) error { nonce := sdb.GetNonce(addr1) if nonce != 1 { return fmt.Errorf("expected addr1 nonce: 1, found nonce: %d", nonce) } transferredFunds := uint256.NewInt(10000) balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) expectedBalance2 := transferredFunds if balance2.Cmp(expectedBalance2) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) } nonce = sdb.GetNonce(addr2) if nonce != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkUpdatedState) } func BuildOnVariousStagesTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr3 = crypto.PubkeyToAddress(key3.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{ addr1: {Balance: genesisBalance}, addr3: {Balance: genesisBalance}, }, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() // This call generates a chain of 3 blocks. signer := types.HomesteadSigner{} genDB, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 20, 10, func(i int, gen *BlockGen) { // Send all funds back and forth between the two accounts if i%2 == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, genesisBalance, params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr1, genesisBalance, params.TxGas, nil, nil), signer, key2) gen.AddTx(tx) } }) if err != nil { t.Fatal(err) } // Build second chain forked off of the 10th block in [chain1] chain2, _, err := GenerateChain(gspec.Config, chain1[9], blockchain.engine, genDB, 10, 10, func(i int, gen *BlockGen) { // Send all funds back and forth between the two accounts if i%2 == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr2, genesisBalance, params.TxGas, nil, nil), signer, key3) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, genesisBalance, params.TxGas, nil, nil), signer, key2) gen.AddTx(tx) } }) if err != nil { t.Fatal(err) } // Build third chain forked off of the 5th block in [chain1]. // The parent of this chain will be accepted before this fork // is inserted. chain3, _, err := GenerateChain(gspec.Config, chain1[4], blockchain.engine, genDB, 10, 10, func(i int, gen *BlockGen) { // Send all funds back and forth between accounts 2 and 3. if i%2 == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, genesisBalance, params.TxGas, nil, nil), signer, key2) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr2, genesisBalance, params.TxGas, nil, nil), signer, key3) gen.AddTx(tx) } }) if err != nil { t.Fatal(err) } // Insert first 10 blocks from [chain1] if _, err := blockchain.InsertChain(chain1); err != nil { t.Fatal(err) } // Accept the first 5 blocks for _, block := range chain1[0:5] { if err := blockchain.Accept(block); err != nil { t.Fatal(err) } } blockchain.DrainAcceptorQueue() // Insert the forked chain [chain2] which starts at the 10th // block in [chain1] ie. a block that is still in processing. if _, err := blockchain.InsertChain(chain2); err != nil { t.Fatal(err) } // Insert another forked chain starting at the last accepted // block from [chain1]. if _, err := blockchain.InsertChain(chain3); err != nil { t.Fatal(err) } // Accept the next block in [chain1] and then reject all // of the blocks in [chain3], which would then be rejected. if err := blockchain.Accept(chain1[5]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() for _, block := range chain3 { if err := blockchain.Reject(block); err != nil { t.Fatal(err) } } // Accept the rest of the blocks in [chain1] for _, block := range chain1[6:10] { if err := blockchain.Accept(block); err != nil { t.Fatal(err) } } blockchain.DrainAcceptorQueue() // Accept the first block in [chain2] and reject the // subsequent blocks in [chain1] which would then be rejected. if err := blockchain.Accept(chain2[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() for _, block := range chain1[10:] { if err := blockchain.Reject(block); err != nil { t.Fatal(err) } } // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce := sdb.GetNonce(addr1) if nonce != 5 { return fmt.Errorf("expected nonce addr1: 5, found nonce: %d", nonce) } balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := genesisBalance if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) expectedBalance2 := genesisBalance if balance2.Cmp(expectedBalance2) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) } nonce = sdb.GetNonce(addr2) if nonce != 5 { return fmt.Errorf("expected addr2 nonce: 5, found nonce: %d", nonce) } balance3 := sdb.GetBalance(addr3) expectedBalance3 := common.U2560 if balance3.Cmp(expectedBalance3) != 0 { return fmt.Errorf("expected addr3 balance: %d, found balance: %d", expectedBalance3, balance3) } nonce = sdb.GetNonce(addr3) if nonce != 1 { return fmt.Errorf("expected addr3 nonce: 1, found nonce: %d", nonce) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func EmptyBlocksTest(t *testing.T, create createFunc) { chainDB := rawdb.NewMemoryDatabase() // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) {}) if err != nil { t.Fatal(err) } // Insert three blocks into the chain and accept only the first block. if _, err := blockchain.InsertChain(chain); err != nil { t.Fatal(err) } for _, block := range chain { if err := blockchain.Accept(block); err != nil { t.Fatal(err) } } blockchain.DrainAcceptorQueue() // Nothing to assert about the state checkState := func(sdb *state.StateDB) error { return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func EmptyAndNonEmptyBlocksTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 5, 10, func(i int, gen *BlockGen) { if i == 3 { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), types.HomesteadSigner{}, key1) gen.AddTx(tx) } }) if err != nil { t.Fatal(err) } if _, err := blockchain.InsertChain(chain); err != nil { t.Fatal(err) } for _, block := range chain { if err := blockchain.Accept(block); err != nil { t.Fatal(err) } } blockchain.DrainAcceptorQueue() // We expect results from block 3 checkState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 1 { return fmt.Errorf("expected addr1 nonce: 3, found nonce: %d", nonce1) } balance1 := sdb.GetBalance(addr1) transferredFunds := uint256.NewInt(10000) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance) != 0 { return fmt.Errorf("expected balance1: %d, found balance: %d", expectedBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce %d", nonce2) } balance2 := sdb.GetBalance(addr2) if balance2.Cmp(transferredFunds) != 0 { return fmt.Errorf("expected balance2: %d, found %d", transferredFunds, balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func ReorgReInsertTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() signer := types.HomesteadSigner{} numBlocks := 3 _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Insert and accept first block if err := blockchain.InsertBlock(chain[0]); err != nil { t.Fatal(err) } if err := blockchain.Accept(chain[0]); err != nil { t.Fatal(err) } // Insert block and then set preference back (rewind) to last accepted blck if err := blockchain.InsertBlock(chain[1]); err != nil { t.Fatal(err) } if err := blockchain.SetPreference(chain[0]); err != nil { t.Fatal(err) } // Re-insert and accept block if err := blockchain.InsertBlock(chain[1]); err != nil { t.Fatal(err) } if err := blockchain.Accept(chain[1]); err != nil { t.Fatal(err) } // Build on top of the re-inserted block and accept if err := blockchain.InsertBlock(chain[2]); err != nil { t.Fatal(err) } if err := blockchain.Accept(chain[2]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() // Nothing to assert about the state checkState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 3 { return fmt.Errorf("expected addr1 nonce: 3, found nonce: %d", nonce1) } balance1 := sdb.GetBalance(addr1) transferredFunds := uint256.NewInt(30000) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance) != 0 { return fmt.Errorf("expected balance1: %d, found balance: %d", expectedBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce %d", nonce2) } balance2 := sdb.GetBalance(addr2) if balance2.Cmp(transferredFunds) != 0 { return fmt.Errorf("expected balance2: %d, found %d", transferredFunds, balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } // Insert two different chains that result in the identical state root. // Once we accept one of the chains, we insert and accept A3 on top of the shared // state root // // G (genesis) // / \ // A1 B1 // | | // A2 B2 (A2 and B2 represent two different paths to the identical state trie) // | // A3 // //nolint:goimports func AcceptBlockIdenticalStateRootTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() signer := types.HomesteadSigner{} _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { if i < 2 { // Send half the funds from addr1 to addr2 in one transaction per each of the two blocks in [chain1] tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(500000000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } // Allow the third block to be empty. }) if err != nil { t.Fatal(err) } _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 2, 10, func(i int, gen *BlockGen) { // Send 1/4 of the funds from addr1 to addr2 in tx1 and 3/4 of the funds in tx2. This will produce the identical state // root in the second block of [chain2] as is present in the second block of [chain1]. if i == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(250000000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(750000000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } }) if err != nil { t.Fatal(err) } // Assert that the block root of the second block in both chains is identical if chain1[1].Root() != chain2[1].Root() { t.Fatalf("Expected the latter block in both chain1 and chain2 to have identical state root, but found %s and %s", chain1[1].Root(), chain2[1].Root()) } // Insert first two blocks of [chain1] and both blocks in [chain2] // This leaves us one additional block to insert on top of [chain1] // after testing that the state roots are handled correctly. if _, err := blockchain.InsertChain(chain1[:2]); err != nil { t.Fatal(err) } if _, err := blockchain.InsertChain(chain2); err != nil { t.Fatal(err) } currentBlock := blockchain.CurrentBlock() expectedCurrentBlock := chain1[1] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } // Accept the first block in [chain1] and reject all of [chain2] if err := blockchain.Accept(chain1[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() for _, block := range chain2 { if err := blockchain.Reject(block); err != nil { t.Fatal(err) } } // Accept the last two blocks in [chain1]. This is a regression test to ensure // that we do not discard a snapshot difflayer that is still in use by a // processing block, when a different block with the same root is rejected. if err := blockchain.Accept(chain1[1]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() lastAcceptedBlock := blockchain.LastConsensusAcceptedBlock() expectedLastAcceptedBlock := chain1[1] if lastAcceptedBlock.Hash() != expectedLastAcceptedBlock.Hash() { t.Fatalf("Expected last accepted block to be %s:%d, but found %s%d", expectedLastAcceptedBlock.Hash().Hex(), expectedLastAcceptedBlock.NumberU64(), lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64()) } if err := blockchain.InsertBlock(chain1[2]); err != nil { t.Fatal(err) } if err := blockchain.Accept(chain1[2]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 2 { return fmt.Errorf("expected addr1 nonce: 2, found nonce: %d", nonce1) } balance1 := sdb.GetBalance(addr1) expectedBalance := common.U2560 if balance1.Cmp(expectedBalance) != 0 { return fmt.Errorf("expected balance1: %d, found balance: %d", expectedBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce %d", nonce2) } balance2 := sdb.GetBalance(addr2) genesisBalance := uint256.MustFromBig(genesisBalance) if balance2.Cmp(genesisBalance) != 0 { return fmt.Errorf("expected balance2: %d, found %d", genesisBalance, balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } // Insert two different chains that result in the identical state root. // Once we insert both of the chains, we restart, insert both the chains again, // and then we accept one of the chains and accept A3 on top of the shared state // root // // G (genesis) // / \ // A1 B1 // | | // A2 B2 (A2 and B2 represent two different paths to the identical state trie) // | // A3 // //nolint:goimports func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } signer := types.HomesteadSigner{} _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { if i < 2 { // Send half the funds from addr1 to addr2 in one transaction per each of the two blocks in [chain1] tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(500000000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } // Allow the third block to be empty. }) if err != nil { t.Fatal(err) } _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 2, 10, func(i int, gen *BlockGen) { // Send 1/4 of the funds from addr1 to addr2 in tx1 and 3/4 of the funds in tx2. This will produce the identical state // root in the second block of [chain2] as is present in the second block of [chain1]. if i == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(250000000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(750000000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) } }) if err != nil { t.Fatal(err) } // Assert that the block root of the second block in both chains is identical if chain1[1].Root() != chain2[1].Root() { t.Fatalf("Expected the latter block in both chain1 and chain2 to have identical state root, but found %s and %s", chain1[1].Root(), chain2[1].Root()) } // Insert first two blocks of [chain1] and both blocks in [chain2] // This leaves us one additional block to insert on top of [chain1] // after testing that the state roots are handled correctly. if _, err := blockchain.InsertChain(chain1[:2]); err != nil { t.Fatal(err) } if _, err := blockchain.InsertChain(chain2); err != nil { t.Fatal(err) } currentBlock := blockchain.CurrentBlock() expectedCurrentBlock := chain1[1] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } blockchain.Stop() // Restart the chain with a new database. chainDB = rawdb.NewMemoryDatabase() blockchain, err = create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() // Insert first two blocks of [chain1] and both blocks in [chain2] // This leaves us one additional block to insert on top of [chain1] // after testing that the state roots are handled correctly. if _, err := blockchain.InsertChain(chain1[:2]); err != nil { t.Fatal(err) } if _, err := blockchain.InsertChain(chain2); err != nil { t.Fatal(err) } currentBlock = blockchain.CurrentBlock() expectedCurrentBlock = chain1[1] if currentBlock.Hash() != expectedCurrentBlock.Hash() { t.Fatalf("Expected current block to be %s:%d, but found %s%d", expectedCurrentBlock.Hash().Hex(), expectedCurrentBlock.NumberU64(), currentBlock.Hash().Hex(), currentBlock.Number.Uint64()) } // Accept the first block in [chain1] and reject all of [chain2] if err := blockchain.Accept(chain1[0]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() for _, block := range chain2 { if err := blockchain.Reject(block); err != nil { t.Fatal(err) } } // Accept the last two blocks in [chain1]. This is a regression test to ensure // that we do not discard a snapshot difflayer that is still in use by a // processing block, when a different block with the same root is rejected. if err := blockchain.Accept(chain1[1]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() lastAcceptedBlock := blockchain.LastConsensusAcceptedBlock() expectedLastAcceptedBlock := chain1[1] if lastAcceptedBlock.Hash() != expectedLastAcceptedBlock.Hash() { t.Fatalf("Expected last accepted block to be %s:%d, but found %s%d", expectedLastAcceptedBlock.Hash().Hex(), expectedLastAcceptedBlock.NumberU64(), lastAcceptedBlock.Hash().Hex(), lastAcceptedBlock.NumberU64()) } if err := blockchain.InsertBlock(chain1[2]); err != nil { t.Fatal(err) } if err := blockchain.Accept(chain1[2]); err != nil { t.Fatal(err) } blockchain.DrainAcceptorQueue() // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce1 := sdb.GetNonce(addr1) if nonce1 != 2 { return fmt.Errorf("expected addr1 nonce: 2, found nonce: %d", nonce1) } balance1 := sdb.GetBalance(addr1) expectedBalance := common.U2560 if balance1.Cmp(expectedBalance) != 0 { return fmt.Errorf("expected balance1: %d, found balance: %d", expectedBalance, balance1) } nonce2 := sdb.GetNonce(addr2) if nonce2 != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce %d", nonce2) } balance2 := sdb.GetBalance(addr2) genesisBalance := uint256.MustFromBig(genesisBalance) if balance2.Cmp(genesisBalance) != 0 { return fmt.Errorf("expected balance2: %d, found %d", genesisBalance, balance2) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func GenerateChainInvalidBlockFeeTest(t *testing.T, create createFunc) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) gspec := &Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) require.NoError(err) t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) _, _, _, err = GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &addr2, Gas: params.TxGas, GasFeeCap: gen.BaseFee(), GasTipCap: big.NewInt(0), Data: []byte{}, }) signedTx, err := types.SignTx(tx, signer, key1) require.NoError(err) gen.AddTx(signedTx) }) require.ErrorIs(err, dummy.ErrInsufficientBlockGas) } func InsertChainInvalidBlockFeeTest(t *testing.T, create createFunc) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) gspec := &Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) require.NoError(err) t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) eng := dummy.NewFakerWithMode(dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}) _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &addr2, Gas: params.TxGas, GasFeeCap: gen.BaseFee(), GasTipCap: big.NewInt(0), Data: []byte{}, }) signedTx, err := types.SignTx(tx, signer, key1) require.NoError(err) gen.AddTx(signedTx) }) require.NoError(err) _, err = blockchain.InsertChain(chain) require.ErrorIs(err, dummy.ErrInsufficientBlockGas) } func InsertChainValidBlockFeeTest(t *testing.T, create createFunc) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) // We use two separate databases since GenerateChain commits the state roots to its underlying // database. chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) gspec := &Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) require.NoError(err) t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) tip := big.NewInt(50000 * params.GWei) transfer := big.NewInt(10000) _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &addr2, Gas: params.TxGas, Value: transfer, GasFeeCap: feeCap, GasTipCap: tip, Data: []byte{}, }) signedTx, err := types.SignTx(tx, signer, key1) require.NoError(err) gen.AddTx(signedTx) }) require.NoError(err) // Insert three blocks into the chain and accept only the first block. _, err = blockchain.InsertChain(chain) require.NoError(err) require.NoError(blockchain.Accept(chain[0])) blockchain.DrainAcceptorQueue() // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce := sdb.GetNonce(addr1) if nonce != 1 { return fmt.Errorf("expected nonce addr1: 1, found nonce: %d", nonce) } balance1 := sdb.GetBalance(addr1) transfer := uint256.MustFromBig(transfer) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transfer) baseFee := chain[0].BaseFee() feeSpend := new(big.Int).Mul(new(big.Int).Add(baseFee, tip), new(big.Int).SetUint64(params.TxGas)) expectedBalance1.Sub(expectedBalance1, uint256.MustFromBig(feeSpend)) if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) expectedBalance2 := transfer if balance2.Cmp(expectedBalance2) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) } nonce = sdb.GetNonce(addr2) if nonce != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce) } return nil } checkBlockChainStateExt(t, blockchain, gspec, chainDB, create, checkState) } func ReexecBlocksTest(t *testing.T, create ReexecTestFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir(), 4096) if err != nil { t.Fatal(err) } defer blockchain.Stop() // This call generates a chain of 10 blocks. signer := types.HomesteadSigner{} _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 10, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Insert three blocks into the chain and accept only the first block. if _, err := blockchain.InsertChain(chain); err != nil { t.Fatal(err) } foundTxs := []common.Hash{} missingTxs := []common.Hash{} for i, block := range chain { if err := blockchain.Accept(block); err != nil { t.Fatal(err) } if i == 3 { // At height 3, kill the async accepted block processor to force an // ungraceful recovery blockchain.stopAcceptor() blockchain.acceptorQueue = nil } if i <= 3 { // If <= height 3, all txs should be accessible on lookup for _, tx := range block.Transactions() { foundTxs = append(foundTxs, tx.Hash()) } } else { // If > 3, all txs should be accessible on lookup for _, tx := range block.Transactions() { missingTxs = append(missingTxs, tx.Hash()) } } } // After inserting all blocks, we should confirm that txs added after the // async worker shutdown cannot be found. for _, tx := range foundTxs { txLookup, _, _ := blockchain.GetTransactionLookup(tx) if txLookup == nil { t.Fatalf("missing transaction: %v", tx) } } for _, tx := range missingTxs { txLookup, _, _ := blockchain.GetTransactionLookup(tx) if txLookup != nil { t.Fatalf("transaction should be missing: %v", tx) } } // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce := sdb.GetNonce(addr1) if nonce != 10 { return fmt.Errorf("expected nonce addr1: 10, found nonce: %d", nonce) } transferredFunds := uint256.MustFromBig(big.NewInt(100000)) balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) expectedBalance2 := transferredFunds if balance2.Cmp(expectedBalance2) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) } nonce = sdb.GetNonce(addr2) if nonce != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce) } return nil } // wrap the create function to set the commit interval checkCreate := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, dataPath string) (*BlockChain, error) { return create(db, gspec, lastAcceptedHash, dataPath, blockchain.cacheConfig.CommitInterval) } _, newChain, restartedChain := checkBlockChainStateExt(t, blockchain, gspec, chainDB, checkCreate, checkState) allTxs := append(foundTxs, missingTxs...) for _, bc := range []*BlockChain{newChain, restartedChain} { // We should confirm that snapshots were properly initialized if bc.snaps == nil && bc.cacheConfig.SnapshotLimit > 0 { t.Fatal("snapshot initialization failed") } // We should confirm all transactions can now be queried for _, tx := range allTxs { txLookup, _, _ := bc.GetTransactionLookup(tx) if txLookup == nil { t.Fatalf("missing transaction: %v", tx) } } } } func ReexecMaxBlocksTest(t *testing.T, create ReexecTestFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: ¶ms.ChainConfig{ ChainConfig: ðparams.ChainConfig{ HomesteadBlock: new(big.Int), }, }, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir(), 4096) if err != nil { t.Fatal(err) } defer blockchain.Stop() // Check that we are generating enough blocks to test the reexec functionality. genNumBlocks := 20 newCommitInterval := 6 numAcceptedBlocks := 2*newCommitInterval - 1 signer := types.HomesteadSigner{} _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, genNumBlocks, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Insert three blocks into the chain and accept only the first block. if _, err := blockchain.InsertChain(chain); err != nil { t.Fatal(err) } foundTxs := []common.Hash{} missingTxs := []common.Hash{} for i, block := range chain { if err := blockchain.Accept(block); err != nil { t.Fatal(err) } if i == numAcceptedBlocks { // kill the async accepted block processor to force an // ungraceful recovery blockchain.stopAcceptor() blockchain.acceptorQueue = nil } if i <= numAcceptedBlocks { // all txs should be accessible on lookup for _, tx := range block.Transactions() { foundTxs = append(foundTxs, tx.Hash()) } } else { // all txs should be accessible on lookup for _, tx := range block.Transactions() { missingTxs = append(missingTxs, tx.Hash()) } } } // After inserting all blocks, we should confirm that txs added after the // async worker shutdown cannot be found. for _, tx := range foundTxs { txLookup, _, _ := blockchain.GetTransactionLookup(tx) if txLookup == nil { t.Fatalf("missing transaction: %v", tx) } } for _, tx := range missingTxs { txLookup, _, _ := blockchain.GetTransactionLookup(tx) if txLookup != nil { t.Fatalf("transaction should be missing: %v", tx) } } // check the state of the last accepted block checkState := func(sdb *state.StateDB) error { nonce := sdb.GetNonce(addr1) if nonce != uint64(genNumBlocks) { return fmt.Errorf("expected nonce addr1: %d, found nonce: %d", genNumBlocks, nonce) } transferredFunds := uint256.MustFromBig(big.NewInt(int64(10000 * genNumBlocks))) balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) expectedBalance2 := transferredFunds if balance2.Cmp(expectedBalance2) != 0 { return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) } nonce = sdb.GetNonce(addr2) if nonce != 0 { return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce) } return nil } checkCreate := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, dataPath string) (*BlockChain, error) { return create(db, gspec, lastAcceptedHash, dataPath, uint64(newCommitInterval)) } _, newChain, restartedChain := checkBlockChainStateExt(t, blockchain, gspec, chainDB, checkCreate, checkState) allTxs := append(foundTxs, missingTxs...) for _, bc := range []*BlockChain{newChain, restartedChain} { // We should confirm that snapshots were properly initialized if bc.snaps == nil && bc.cacheConfig.SnapshotLimit > 0 { t.Fatal("snapshot initialization failed") } // We should confirm all transactions can now be queried for _, tx := range allTxs { txLookup, _, _ := bc.GetTransactionLookup(tx) if txLookup == nil { t.Fatalf("missing transaction: %v", tx) } } } }