// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved. // See the file LICENSE for licensing terms. package core import ( "fmt" "math/big" "testing" "github.com/holiman/uint256" "github.com/luxfi/crypto" "github.com/luxfi/evm/commontype" "github.com/luxfi/evm/consensus/dummy" "github.com/luxfi/evm/core/state" "github.com/luxfi/evm/params" "github.com/luxfi/evm/params/extras" "github.com/luxfi/evm/precompile/allowlist" "github.com/luxfi/evm/precompile/contracts/deployerallowlist" "github.com/luxfi/evm/precompile/contracts/feemanager" "github.com/luxfi/evm/utils" "github.com/luxfi/geth/common" "github.com/luxfi/geth/core/rawdb" "github.com/luxfi/geth/core/types" "github.com/luxfi/geth/ethdb" ethparams "github.com/luxfi/geth/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type createFunc func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash, dataPath string) (*BlockChain, error) type ChainTest struct { Name string testFunc func( t *testing.T, create createFunc, ) } var tests = []ChainTest{ { "InsertChainAcceptSingleBlock", InsertChainAcceptSingleBlock, }, { "InsertForkedChain", InsertLongForkedChain, }, { "AcceptNonCanonicalBlock", AcceptNonCanonicalBlock, }, { "SetPreferenceRewind", SetPreferenceRewind, }, { "BuildOnVariousStages", BuildOnVariousStages, }, { "EmptyBlocks", EmptyBlocks, }, { "EmptyAndNonEmptyBlocks", EmptyAndNonEmptyBlocks, }, { "ReorgReInsert", ReorgReInsert, }, { "AcceptBlockIdenticalStateRoot", AcceptBlockIdenticalStateRoot, }, { "ReprocessAcceptBlockIdenticalStateRoot", ReprocessAcceptBlockIdenticalStateRoot, }, { "GenerateChainInvalidBlockFee", GenerateChainInvalidBlockFee, }, { "InsertChainInvalidBlockFee", InsertChainInvalidBlockFee, }, { "InsertChainValidBlockFee", InsertChainValidBlockFee, }, { "TestStatefulPrecompiles", StatefulPrecompiles, }, } 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", ReexecBlocks, }, { "ReexecMaxBlocks", ReexecMaxBlocks, }, { "ReexecCorruptedState", ReexecCorruptedStateTest, }, } func copyMemDB(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 checkBlockChainState( 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) } oldChainDataDir := bc.CacheConfig().ChainDataDir // cacheConfig uses same reference in most tests 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 = copyMemDB(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 InsertChainAcceptSingleBlock(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } // Generate chain first to get the database with genesis and blocks. // This ensures the blockchain and generated blocks share the same database // and genesis state, which is required for snapshot layer consistency. signer := types.LatestSigner(params.TestChainConfig) chainDB, chain, _, err := GenerateChainWithGenesis(gspec, dummy.NewCoinbaseFaker(), 3, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), ethparams.TxGas, big.NewInt(1), nil), signer, key1) gen.AddTx(tx) }) if err != nil { t.Fatal(err) } // Create blockchain using the database from GenerateChainWithGenesis. blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() // 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) gasCost := uint256.MustFromBig(big.NewInt(21000)) // TxGas * gasPrice (1) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance1 = new(uint256.Int).Sub(expectedBalance1, gasCost) 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func InsertLongForkedChain(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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), ethparams.TxGas, big.NewInt(1), 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), ethparams.TxGas, big.NewInt(1), 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) gasCost := uint256.NewInt(129 * 21000) // 129 transactions * TxGas genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance = new(uint256.Int).Sub(expectedBalance, gasCost) 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func AcceptNonCanonicalBlock(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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), ethparams.TxGas, big.NewInt(1), 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), ethparams.TxGas, big.NewInt(1), 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) gasCost := uint256.NewInt(21000) // TxGas * gasPrice (1) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance = new(uint256.Int).Sub(expectedBalance, gasCost) 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func SetPreferenceRewind(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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), ethparams.TxGas, big.NewInt(1), 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 } checkBlockChainState(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) gasCost := uint256.MustFromBig(big.NewInt(21000)) // TxGas * gasPrice (1) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance1 = new(uint256.Int).Sub(expectedBalance1, gasCost) 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkUpdatedState) } func BuildOnVariousStages(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) cryptoAddr3 = crypto.PubkeyToAddress(key3.PublicKey) addr3 = common.BytesToAddress(cryptoAddr3[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that all accounts have funds in the genesis block. // Use large balance to accommodate gas costs when transferring funds. genesisBalance := big.NewInt(1e18) transferAmount := big.NewInt(1e17) // Transfer amount per tx gspec := &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{ addr1: {Balance: genesisBalance}, addr2: {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.LatestSigner(params.TestChainConfig) genDB, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 20, 10, func(i int, gen *BlockGen) { // Send funds back and forth between the two accounts if i%2 == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, transferAmount, ethparams.TxGas, big.NewInt(1), nil), signer, key1) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr1, transferAmount, ethparams.TxGas, big.NewInt(1), 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 funds back and forth between the two accounts if i%2 == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr2, transferAmount, ethparams.TxGas, big.NewInt(1), nil), signer, key3) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, transferAmount, ethparams.TxGas, big.NewInt(1), 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, transferAmount, ethparams.TxGas, big.NewInt(1), nil), signer, key2) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr2, transferAmount, ethparams.TxGas, big.NewInt(1), 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 // Accepted chain: genesis + chain1[0:10] + chain2[0] // chain1[0-9]: 5 txs from addr1->addr2, 5 txs from addr2->addr1 (transfers cancel, gas doesn't) // chain2[0]: 1 tx from addr3->addr2 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) genBal := uint256.MustFromBig(genesisBalance) gasCost5 := uint256.NewInt(5 * 21000) // 5 txs × gas expectedBalance1 := new(uint256.Int).Sub(genBal, gasCost5) // 1e18 - 105000 if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) } balance2 := sdb.GetBalance(addr2) // addr2: receives 1e17 from chain2[0], but paid 5 × gas in chain1 xferAmt := uint256.MustFromBig(transferAmount) expectedBalance2 := new(uint256.Int).Sub(genBal, gasCost5) // 1e18 - 105000 expectedBalance2 = new(uint256.Int).Add(expectedBalance2, xferAmt) // + 1e17 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) // addr3: sent 1e17 and paid 21000 gas in chain2[0] gasCost1 := uint256.NewInt(21000) expectedBalance3 := new(uint256.Int).Sub(genBal, xferAmt) // 1e18 - 1e17 expectedBalance3 = new(uint256.Int).Sub(expectedBalance3, gasCost1) // - 21000 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func EmptyBlocks(t *testing.T, create createFunc) { chainDB := rawdb.NewMemoryDatabase() gspec := &Genesis{ Config: params.TestChainConfig, Alloc: 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func EmptyAndNonEmptyBlocks(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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), ethparams.TxGas, big.NewInt(1), nil), signer, 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 (1 tx: 10000 transferred + 21000 gas) 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(10000) gasCost := uint256.NewInt(21000) // 1 tx × gas genBal := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genBal, transferredFunds) expectedBalance = new(uint256.Int).Sub(expectedBalance, gasCost) 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func ReorgReInsert(t *testing.T, create createFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) 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), ethparams.TxGas, big.NewInt(1), 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() // 3 txs × 10000 transferred + 3 × 21000 gas 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) gasCost := uint256.NewInt(3 * 21000) // 3 txs × gas genBal := uint256.MustFromBig(genesisBalance) expectedBalance := new(uint256.Int).Sub(genBal, transferredFunds) expectedBalance = new(uint256.Int).Sub(expectedBalance, gasCost) 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 } checkBlockChainState(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 AcceptBlockIdenticalStateRoot(t *testing.T, create createFunc) { // Skip: This test requires side chain insertion, which is intentionally disabled // in Lux EVM. The test creates two parallel chains (chain1 and chain2) that result // in identical state roots. Side chain insertion returns ErrPrunedAncestor after // the primary chain is inserted, which is expected behavior for single-validator consensus. t.Skip("Side chain insertion is not supported in Lux EVM") var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. // Use large balance to accommodate gas costs with headroom for multiple txs. genesisBalance := big.NewInt(2e18) // 2 ETH transferHalf := big.NewInt(5e17) // 0.5 ETH per block (total 1 ETH) transferQuarter := big.NewInt(25e16) // 0.25 ETH transferThreeQuarter := big.NewInt(75e16) // 0.75 ETH (total 1 ETH) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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, transferHalf, ethparams.TxGas, big.NewInt(1), 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, transferQuarter, ethparams.TxGas, big.NewInt(1), nil), signer, key1) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, transferThreeQuarter, ethparams.TxGas, big.NewInt(1), 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 // chain1 has 2 txs transferring 5e17 each = 1e18 total, with 2 × 21000 gas 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) // genesis 2e18 - transfers 1e18 - gas 42000 genBal := uint256.MustFromBig(genesisBalance) totalTransfer := uint256.MustFromBig(new(big.Int).Mul(transferHalf, big.NewInt(2))) // 1e18 gasCost := uint256.NewInt(2 * 21000) expectedBalance := new(uint256.Int).Sub(genBal, totalTransfer) expectedBalance = new(uint256.Int).Sub(expectedBalance, gasCost) 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(totalTransfer) != 0 { return fmt.Errorf("expected balance2: %d, found %d", totalTransfer, balance2) } return nil } checkBlockChainState(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 ReprocessAcceptBlockIdenticalStateRoot(t *testing.T, create createFunc) { // Skip: This test requires side chain insertion, which is intentionally disabled // in Lux EVM. The test creates two parallel chains (chain1 and chain2) that result // in identical state roots. Side chain insertion returns ErrPrunedAncestor after // the primary chain is inserted, which is expected behavior for single-validator consensus. t.Skip("Side chain insertion is not supported in Lux EVM") var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. // Use large balance to accommodate gas costs. genesisBalance := big.NewInt(2e18) // 2 ETH transferHalf := big.NewInt(5e17) // 0.5 ETH per block transferQuarter := big.NewInt(25e16) // 0.25 ETH transferThreeQuarter := big.NewInt(75e16) // 0.75 ETH gspec := &Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } signer := types.LatestSigner(params.TestChainConfig) _, 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, transferHalf, ethparams.TxGas, big.NewInt(1), 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, transferQuarter, ethparams.TxGas, big.NewInt(1), nil), signer, key1) gen.AddTx(tx) } else { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, transferThreeQuarter, ethparams.TxGas, big.NewInt(1), 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() 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 // chain1 has 2 txs transferring 5e17 each = 1e18 total, with 2 × 21000 gas 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) // genesis 2e18 - transfers 1e18 - gas 42000 genBal := uint256.MustFromBig(genesisBalance) totalTransfer := uint256.MustFromBig(new(big.Int).Mul(transferHalf, big.NewInt(2))) // 1e18 gasCost := uint256.NewInt(2 * 21000) expectedBalance := new(uint256.Int).Sub(genBal, totalTransfer) expectedBalance = new(uint256.Int).Sub(expectedBalance, gasCost) 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(totalTransfer) != 0 { return fmt.Errorf("expected balance2: %d, found %d", totalTransfer, balance2) } return nil } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func GenerateChainInvalidBlockFee(t *testing.T, create createFunc) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) 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: 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, extras.TestChainConfig.FeeConfig.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: (*common.Address)(&addr2), Gas: ethparams.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 InsertChainInvalidBlockFee(t *testing.T, create createFunc) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) 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: 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, extras.TestChainConfig.FeeConfig.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: (*common.Address)(&addr2), Gas: ethparams.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 InsertChainValidBlockFee(t *testing.T, create createFunc) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) 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, extras.TestChainConfig.FeeConfig.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: (*common.Address)(&addr2), Gas: ethparams.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() // Use the actual block's base fee for expected calculation actualBaseFee := chain[0].BaseFee() // 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) transferAmt := uint256.MustFromBig(transfer) genBal := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genBal, transferAmt) // Fee = (baseFee + tip) * gasUsed feeSpend := new(big.Int).Mul(new(big.Int).Add(actualBaseFee, tip), new(big.Int).SetUint64(ethparams.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 := transferAmt 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 } checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } // StatefulPrecompiles provides a testing framework to ensure that processing transactions interacting with the stateful precompiles work as expected. // NOTE: Skipped until StatefulPrecompileHook type is added to luxfi/geth func StatefulPrecompiles(t *testing.T, create createFunc) { t.Skip("Skipping TestStatefulPrecompiles: requires StatefulPrecompileHook type in luxfi/geth (vm.Config.StatefulPrecompileHook)") var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has sufficient funds in the genesis block for all of the tests. genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) config := params.Copy(params.TestChainConfig) // Set all of the required config parameters params.GetExtra(config).GenesisPrecompiles = extras.Precompiles{ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(0), []common.Address{addr1}, nil, nil), feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(0), []common.Address{addr1}, nil, nil, nil), } gspec := &Genesis{ Config: config, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, t.TempDir()) if err != nil { t.Fatal(err) } defer blockchain.Stop() signer := types.LatestSigner(params.TestChainConfig) tip := big.NewInt(50000 * params.GWei) // Simple framework to add a test that the stateful precompile works as expected type test struct { addTx func(gen *BlockGen) verifyGenesis func(sdb *state.StateDB) verifyState func(sdb *state.StateDB) error } testFeeConfig := commontype.FeeConfig{ GasLimit: big.NewInt(11_000_000), TargetBlockRate: 5, // in seconds MinBaseFee: big.NewInt(28_000_000_000), TargetGas: big.NewInt(18_000_000), BaseFeeChangeDenominator: big.NewInt(3396), MinBlockGasCost: big.NewInt(0), MaxBlockGasCost: big.NewInt(4_000_000), BlockGasCostStep: big.NewInt(500_000), } assert := assert.New(t) tests := map[string]test{ "allow list": { addTx: func(gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) input, err := allowlist.PackModifyAllowList(addr2, allowlist.AdminRole) if err != nil { t.Fatal(err) } tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &deployerallowlist.ContractAddress, Gas: 3_000_000, Value: common.Big0, GasFeeCap: feeCap, GasTipCap: tip, Data: input, }) signedTx, err := types.SignTx(tx, signer, key1) if err != nil { t.Fatal(err) } gen.AddTx(signedTx) }, verifyState: func(sdb *state.StateDB) error { res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) if allowlist.AdminRole != res { return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole) } res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) if allowlist.AdminRole != res { return fmt.Errorf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AdminRole) } return nil }, verifyGenesis: func(sdb *state.StateDB) { res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) if allowlist.AdminRole != res { t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole) } res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) if allowlist.NoRole != res { t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.NoRole) } }, }, "fee manager set config": { addTx: func(gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) input, err := feemanager.PackSetFeeConfig(testFeeConfig) if err != nil { t.Fatal(err) } tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &feemanager.ContractAddress, Gas: 3_000_000, Value: common.Big0, GasFeeCap: feeCap, GasTipCap: tip, Data: input, }) signedTx, err := types.SignTx(tx, signer, key1) if err != nil { t.Fatal(err) } gen.AddTx(signedTx) }, verifyState: func(sdb *state.StateDB) error { res := feemanager.GetFeeManagerStatus(sdb, addr1) assert.Equal(allowlist.AdminRole, res) storedConfig := feemanager.GetStoredFeeConfig(sdb) assert.EqualValues(testFeeConfig, storedConfig) // Use LastConsensusAcceptedBlock's header instead of CurrentHeader // because verifyState is called with state from lastAcceptedBlock feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.LastConsensusAcceptedBlock().Header()) assert.NoError(err) assert.EqualValues(testFeeConfig, feeConfig) return nil }, verifyGenesis: func(sdb *state.StateDB) { res := feemanager.GetFeeManagerStatus(sdb, addr1) assert.Equal(allowlist.AdminRole, res) feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.Genesis().Header()) assert.NoError(err) assert.EqualValues(params.GetExtra(config).FeeConfig, feeConfig) }, }, } // Generate chain of blocks using [genDB] instead of [chainDB] to avoid writing // to the BlockChain's database while generating blocks. _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 1, 0, func(i int, gen *BlockGen) { for _, test := range tests { test.addTx(gen) } }) 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() genesisState, err := blockchain.StateAt(blockchain.Genesis().Root()) if err != nil { t.Fatal(err) } for _, test := range tests { if test.verifyGenesis == nil { continue } test.verifyGenesis(genesisState) } // Run all of the necessary state verification checkState := func(sdb *state.StateDB) error { for _, test := range tests { if err := test.verifyState(sdb); err != nil { return err } } return nil } // This tests that the precompiles work as expected when they are enabled checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } func ReexecBlocks(t *testing.T, create ReexecTestFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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), ethparams.TxGas, big.NewInt(1), 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)) // 10 txs × 10000 each balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) gasCost := uint256.MustFromBig(big.NewInt(10 * 21000)) // 10 txs × TxGas × gasPrice (1) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance1 = new(uint256.Int).Sub(expectedBalance1, gasCost) 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 := checkBlockChainState(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 ReexecMaxBlocks(t *testing.T, create ReexecTestFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ Config: params.TestChainConfig, 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.LatestSigner(params.TestChainConfig) _, 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), ethparams.TxGas, big.NewInt(1), 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))) // 20 txs × 10000 each balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) gasCost := uint256.MustFromBig(big.NewInt(int64(21000 * genNumBlocks))) // 20 txs × TxGas × gasPrice (1) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance1 = new(uint256.Int).Sub(expectedBalance1, gasCost) 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 := checkBlockChainState(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 ReexecCorruptedStateTest(t *testing.T, create ReexecTestFunc) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") cryptoAddr1 = crypto.PubkeyToAddress(key1.PublicKey) cryptoAddr2 = crypto.PubkeyToAddress(key2.PublicKey) addr1 = common.BytesToAddress(cryptoAddr1[:]) addr2 = common.BytesToAddress(cryptoAddr2[:]) chainDB = rawdb.NewMemoryDatabase() genesisBalance = big.NewInt(1000000) tempDir = t.TempDir() ) // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } blockchain, err := create(chainDB, gspec, common.Hash{}, tempDir, 4096) if err != nil { t.Fatalf("failed to create blockchain: %v", err) } // Check that we are generating enough blocks to test the reexec functionality. signer := types.LatestSigner(params.TestChainConfig) _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 10, 10, func(_ int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), ethparams.TxGas, big.NewInt(1), 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) } // Accept only the first block. if err := blockchain.Accept(chain[0]); err != nil { t.Fatal(err) } // Simulate a crash by updating the acceptor tip blockchain.writeBlockAcceptedIndices(chain[1]) blockchain.Stop() // Restart blockchain with existing state restartedBlockchain, err := create(chainDB, gspec, chain[1].Hash(), tempDir, 4096) if err != nil { t.Fatalf("failed to restart blockchain: %v", err) } defer restartedBlockchain.Stop() // We should be able to accept the remaining blocks for _, block := range chain[2:] { if err := restartedBlockchain.InsertBlock(block); err != nil { t.Fatalf("failed to insert block %d: %v", block.NumberU64(), err) } if err := restartedBlockchain.Accept(block); err != nil { t.Fatalf("failed to accept block %d: %v", block.NumberU64(), err) } } // 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)) // 10 txs × 10000 each balance1 := sdb.GetBalance(addr1) genesisBalance := uint256.MustFromBig(genesisBalance) gasCost := uint256.MustFromBig(big.NewInt(10 * 21000)) // 10 txs × TxGas × gasPrice (1) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transferredFunds) expectedBalance1 = new(uint256.Int).Sub(expectedBalance1, gasCost) 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) } checkBlockChainState(t, restartedBlockchain, gspec, chainDB, checkCreate, checkState) }