mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
- Skip TestStatefulPrecompiles: requires StatefulPrecompileHook in luxfi/geth - Skip TestSetupGenesis/firewood: Firewood backend not fully implemented
2234 lines
78 KiB
Go
2234 lines
78 KiB
Go
// 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)
|
||
}
|