diff --git a/core/bench_test.go b/core/bench_test.go index edd3ae430d..a68fa9e7e5 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -32,8 +32,10 @@ import ( "math/big" "testing" + "github.com/ava-labs/avalanchego/x/blockdb" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/common/math" "github.com/ava-labs/libevm/core/rawdb" @@ -160,9 +162,11 @@ func genTxRing(naccounts int) func(int, *BlockGen) { func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Create the database in memory or in a temporary directory. var db ethdb.Database + var blockDb ethblockdb.Database var err error if !disk { db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) } else { dir := b.TempDir() db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false) @@ -170,6 +174,11 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { b.Fatalf("cannot create temporary database: %v", err) } defer db.Close() + store, err := blockdb.New(blockdb.DefaultConfig().WithDir(dir), nil) + if err != nil { + b.Fatalf("cannot create temporary block database: %v", err) + } + blockDb = ethblockdb.New(store, db) } // Generate a chain of b.N blocks using the supplied block @@ -178,11 +187,11 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { Config: params.TestChainConfig, Alloc: types.GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, } - _, chain, _, _ := GenerateChainWithGenesis(gspec, dummy.NewCoinbaseFaker(), b.N, 10, gen) + _, _, chain, _, _ := GenerateChainWithGenesis(gspec, dummy.NewCoinbaseFaker(), b.N, 10, gen) // Time the insertion of the new chain. // State and blocks are stored in the same DB. - chainman, _ := NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) + chainman, _ := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) defer chainman.Stop() b.ReportAllocs() b.ResetTimer() @@ -283,6 +292,11 @@ func benchReadChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } + store, err := blockdb.New(blockdb.DefaultConfig().WithDir(dir), nil) + if err != nil { + b.Fatalf("cannot create temporary block database: %v", err) + } + blockDb := ethblockdb.New(store, db) genesis := &Genesis{Config: params.TestChainConfig} makeChainForBench(db, genesis, full, count) db.Close() @@ -295,7 +309,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } - chain, err := NewBlockChain(db, DefaultCacheConfig, genesis, dummy.NewFaker(), vm.Config{}, common.Hash{}, false) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, genesis, dummy.NewFaker(), vm.Config{}, common.Hash{}, false) if err != nil { b.Fatalf("error creating chain: %v", err) } diff --git a/core/blockchain.go b/core/blockchain.go index a8b3aa0c14..9be86639d3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -49,6 +49,7 @@ import ( "github.com/ava-labs/coreth/plugin/evm/customlogs" "github.com/ava-labs/coreth/plugin/evm/customrawdb" "github.com/ava-labs/coreth/plugin/evm/customtypes" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/acp176" "github.com/ava-labs/coreth/triedb/hashdb" "github.com/ava-labs/coreth/triedb/pathdb" @@ -67,6 +68,7 @@ import ( // Force libevm metrics of the same name to be registered first. _ "github.com/ava-labs/libevm/core" + _ "github.com/ava-labs/libevm/trie" ) // ====== If resolving merge conflicts ====== @@ -269,11 +271,12 @@ type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning - db ethdb.Database // Low level persistent database to store final content in - snaps *snapshot.Tree // Snapshot tree for fast trie leaf access - triedb *triedb.Database // The database handler for maintaining trie nodes. - stateCache state.Database // State database to reuse between imports (contains state cache) - txIndexer *txIndexer // Transaction indexer, might be nil if not enabled + db ethdb.Database // Low level persistent database to store final content in + blockDb ethblockdb.Database // Block database for canonical blocks + snaps *snapshot.Tree // Snapshot tree for fast trie leaf access + triedb *triedb.Database // The database handler for maintaining trie nodes. + stateCache state.Database // State database to reuse between imports (contains state cache) + txIndexer *txIndexer // Transaction indexer, might be nil if not enabled stateManager TrieWriter hc *HeaderChain @@ -360,7 +363,7 @@ type BlockChain struct { // available in the database. It initialises the default Ethereum Validator and // Processor. func NewBlockChain( - db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, engine consensus.Engine, + db ethdb.Database, blockDb ethblockdb.Database, cacheConfig *CacheConfig, genesis *Genesis, engine consensus.Engine, vmConfig vm.Config, lastAcceptedHash common.Hash, skipChainConfigCheckCompatible bool, ) (*BlockChain, error) { if cacheConfig == nil { @@ -375,7 +378,7 @@ func NewBlockChain( // Note: In go-ethereum, the code rewinds the chain on an incompatible config upgrade. // We don't do this and expect the node operator to always update their node's configuration // before network upgrades take effect. - chainConfig, _, err := SetupGenesisBlock(db, triedb, genesis, lastAcceptedHash, skipChainConfigCheckCompatible) + chainConfig, _, err := SetupGenesisBlock(db, triedb, blockDb, genesis, lastAcceptedHash, skipChainConfigCheckCompatible) if err != nil { return nil, err } @@ -391,6 +394,7 @@ func NewBlockChain( chainConfig: chainConfig, cacheConfig: cacheConfig, db: db, + blockDb: blockDb, triedb: triedb, bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), @@ -408,7 +412,7 @@ func NewBlockChain( bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.processor = NewStateProcessor(chainConfig, bc, engine) - bc.hc, err = NewHeaderChain(db, chainConfig, cacheConfig, engine) + bc.hc, err = NewHeaderChain(db, blockDb, chainConfig, cacheConfig, engine) if err != nil { return nil, err } @@ -738,11 +742,7 @@ func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error { func (bc *BlockChain) loadGenesisState() error { // Prepare the genesis block and reinitialise the chain - batch := bc.db.NewBatch() - rawdb.WriteBlock(batch, bc.genesisBlock) - if err := batch.Write(); err != nil { - log.Crit("Failed to write genesis block", "err", err) - } + bc.blockDb.WriteBlock(bc.genesisBlock) bc.writeHeadBlock(bc.genesisBlock) // Last update all in-memory chain markers @@ -1082,6 +1082,26 @@ func (bc *BlockChain) Accept(block *types.Block) error { } } + // Since we optimistically store the first block at a height as the canonical block, + // we need to rewrite the canonical block in the blockdb if another block at the same height + // was accepted. + savedBlock := bc.blockDb.ReadBlock(block.NumberU64()) + if savedBlock == nil || savedBlock.Hash() != block.Hash() { + bc.blockDb.WriteBlock(block) + + // delete the block data from the chaindb since we are now storing it in the blockdb + batch := bc.db.NewBatch() + rawdb.DeleteHeader(batch, block.Hash(), block.NumberU64()) + rawdb.DeleteBody(batch, block.Hash(), block.NumberU64()) + // add back the hash->number mapping since DeleteHeader will have removed it + rawdb.WriteHeaderNumber(batch, block.Hash(), block.NumberU64()) + // save the sidechain block back to the chaindb + rawdb.WriteBlock(batch, savedBlock) + if err := batch.Write(); err != nil { + return fmt.Errorf("failed to write delete block batch: %w", err) + } + } + // Enqueue block in the acceptor bc.lastAccepted = block bc.addAcceptorQueue(block) @@ -1103,6 +1123,7 @@ func (bc *BlockChain) Accept(block *types.Block) error { latestGasCapacityGauge.Update(int64(s.Gas.Capacity)) latestGasTargetGauge.Update(int64(s.Target())) } + return nil } @@ -1188,16 +1209,23 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, parentRoot common // writeBlockWithState writes the block and all associated state to the database, // but it expects the chain mutex to be held. func (bc *BlockChain) writeBlockWithState(block *types.Block, parentRoot common.Hash, receipts []*types.Receipt, state *state.StateDB) error { - // Irrelevant of the canonical status, write the block itself to the database. + // Irrelevant of the canonical status, write the block to blockdb and related + // data to the chaindb. // - // Note all the components of block(hash->number map, header, body, receipts) + // Note the block data is written first, then all other components of block(hash->number map, receipts, preimages) // should be written atomically. BlockBatch is used for containing all components. blockBatch := bc.db.NewBatch() - rawdb.WriteBlock(blockBatch, block) + blockNumber := block.NumberU64() + if !bc.blockDb.HasBlock(blockNumber) { + bc.blockDb.WriteBlock(block) + } else { + // write block to the chaindb if we already stored a block at the same height + rawdb.WriteBlock(bc.db, block) + } rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) rawdb.WritePreimages(blockBatch, state.Preimages()) if err := blockBatch.Write(); err != nil { - log.Crit("Failed to write block into disk", "err", err) + log.Crit("Failed to write block receipts and preimages into disk", "err", err) } // Commit all cached state changes into underlying memory database. @@ -1538,12 +1566,15 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { // stale lookups are still cached. bc.txLookupCache.Purge() - // Insert the new chain(except the head block(reverse order)), - // taking care of the proper incremental order. + // Remove non-canonical blocks from the chain db and ensure the canonical + // block are stored in the blockdb + blocksBatch := bc.db.NewBatch() for i := len(newChain) - 1; i >= 1; i-- { - // Insert the block in the canonical way, re-writing history bc.writeHeadBlock(newChain[i]) } + if err := blocksBatch.Write(); err != nil { + log.Crit("Failed to write block data during reorg", "err", err) + } // Delete any canonical number assignments above the new head indexesBatch := bc.db.NewBatch() diff --git a/core/blockchain_ext_test.go b/core/blockchain_ext_test.go index f03917d5f5..4b78a8ecf0 100644 --- a/core/blockchain_ext_test.go +++ b/core/blockchain_ext_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap4" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -41,7 +42,7 @@ type ChainTest struct { Name string testFunc func( t *testing.T, - create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error), + create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error), ) } @@ -119,12 +120,14 @@ func checkBlockChainState( bc *BlockChain, gspec *Genesis, originalDB ethdb.Database, - create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error), + originalBlockDb ethblockdb.Database, + create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error), checkState func(sdb *state.StateDB) error, ) (*BlockChain, *BlockChain, *BlockChain) { var ( lastAcceptedBlock = bc.LastConsensusAcceptedBlock() newDB = rawdb.NewMemoryDatabase() + newBlockDb = ethblockdb.NewMock(newDB) ) acceptedState, err := bc.StateAt(lastAcceptedBlock.Root()) @@ -135,7 +138,7 @@ func checkBlockChainState( t.Fatalf("Check state failed for original blockchain due to: %s", err) } - newBlockChain, err := create(newDB, gspec, common.Hash{}) + newBlockChain, err := create(newDB, newBlockDb, gspec, common.Hash{}) if err != nil { t.Fatalf("Failed to create new blockchain instance: %s", err) } @@ -174,7 +177,8 @@ func checkBlockChainState( if err != nil { t.Fatal(err) } - restartedChain, err := create(originalDB, gspec, lastAcceptedBlock.Hash()) + originalBlockDb = ethblockdb.Copy(originalBlockDb, originalDB) + restartedChain, err := create(originalDB, originalBlockDb, gspec, lastAcceptedBlock.Hash()) if err != nil { t.Fatal(err) } @@ -198,13 +202,14 @@ func checkBlockChainState( return bc, newBlockChain, restartedChain } -func InsertChainAcceptSingleBlockTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func InsertChainAcceptSingleBlockTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -213,7 +218,7 @@ func InsertChainAcceptSingleBlockTest(t *testing.T, create func(db ethdb.Databas Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -221,7 +226,7 @@ func InsertChainAcceptSingleBlockTest(t *testing.T, create func(db ethdb.Databas // This call generates a chain of 3 blocks. signer := types.HomesteadSigner{} - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) @@ -265,16 +270,17 @@ func InsertChainAcceptSingleBlockTest(t *testing.T, create func(db ethdb.Databas return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } -func InsertLongForkedChainTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func InsertLongForkedChainTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -284,7 +290,7 @@ func InsertLongForkedChainTest(t *testing.T, create func(db ethdb.Database, gspe Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -292,7 +298,7 @@ func InsertLongForkedChainTest(t *testing.T, create func(db ethdb.Database, gspe numBlocks := 129 signer := types.HomesteadSigner{} - _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { + _, _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -302,7 +308,7 @@ func InsertLongForkedChainTest(t *testing.T, create func(db ethdb.Database, gspe } // 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) { + _, _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks+1, 10, func(i int, gen *BlockGen) { // Generate a transaction with a different amount to ensure [chain2] is different than [chain1]. tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(5000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -429,10 +435,10 @@ func InsertLongForkedChainTest(t *testing.T, create func(db ethdb.Database, gspe return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } -func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") @@ -441,6 +447,7 @@ func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, gs // We use two separate databases since GenerateChain commits the state roots to its underlying // database. chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -450,7 +457,7 @@ func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, gs Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -458,7 +465,7 @@ func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, gs numBlocks := 3 signer := types.HomesteadSigner{} - _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { + _, _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -466,7 +473,7 @@ func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, gs if err != nil { t.Fatal(err) } - _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { + _, _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction with a different amount to create a chain of blocks different from [chain1] tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(5000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -540,16 +547,17 @@ func AcceptNonCanonicalBlockTest(t *testing.T, create func(db ethdb.Database, gs return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } -func SetPreferenceRewindTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func SetPreferenceRewindTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -559,7 +567,7 @@ func SetPreferenceRewindTest(t *testing.T, create func(db ethdb.Database, gspec Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -567,7 +575,7 @@ func SetPreferenceRewindTest(t *testing.T, create func(db ethdb.Database, gspec numBlocks := 3 signer := types.HomesteadSigner{} - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -632,7 +640,7 @@ func SetPreferenceRewindTest(t *testing.T, create func(db ethdb.Database, gspec } return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkGenesisState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkGenesisState) if err := blockchain.Accept(chain[0]); err != nil { t.Fatal(err) @@ -672,10 +680,10 @@ func SetPreferenceRewindTest(t *testing.T, create func(db ethdb.Database, gspec } return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkUpdatedState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkUpdatedState) } -func BuildOnVariousStagesTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func BuildOnVariousStagesTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") @@ -684,6 +692,7 @@ func BuildOnVariousStagesTest(t *testing.T, create func(db ethdb.Database, gspec addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr3 = crypto.PubkeyToAddress(key3.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -696,7 +705,7 @@ func BuildOnVariousStagesTest(t *testing.T, create func(db ethdb.Database, gspec }, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -704,7 +713,7 @@ func BuildOnVariousStagesTest(t *testing.T, create func(db ethdb.Database, gspec // This call generates a chain of 3 blocks. signer := types.HomesteadSigner{} - genDB, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 20, 10, func(i int, gen *BlockGen) { + genDB, _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 20, 10, func(i int, gen *BlockGen) { // Send all funds back and forth between the two accounts if i%2 == 0 { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, genesisBalance, params.TxGas, nil, nil), signer, key1) @@ -839,11 +848,12 @@ func BuildOnVariousStagesTest(t *testing.T, create func(db ethdb.Database, gspec return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } -func EmptyBlocksTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func EmptyBlocksTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { chainDB := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(chainDB) // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ @@ -851,13 +861,13 @@ func EmptyBlocksTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis Alloc: types.GenesisAlloc{}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } defer blockchain.Stop() - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) {}) + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) {}) if err != nil { t.Fatal(err) } @@ -878,16 +888,17 @@ func EmptyBlocksTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } -func ReorgReInsertTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func ReorgReInsertTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -897,7 +908,7 @@ func ReorgReInsertTest(t *testing.T, create func(db ethdb.Database, gspec *Genes Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -905,7 +916,7 @@ func ReorgReInsertTest(t *testing.T, create func(db ethdb.Database, gspec *Genes signer := types.HomesteadSigner{} numBlocks := 3 - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -971,7 +982,7 @@ func ReorgReInsertTest(t *testing.T, create func(db ethdb.Database, gspec *Genes return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } // Insert two different chains that result in the identical state root. @@ -987,13 +998,14 @@ func ReorgReInsertTest(t *testing.T, create func(db ethdb.Database, gspec *Genes // A3 // //nolint:goimports -func AcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func AcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -1003,14 +1015,14 @@ func AcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Databa Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } defer blockchain.Stop() signer := types.HomesteadSigner{} - _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { + _, _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { if i < 2 { // Send half the funds from addr1 to addr2 in one transaction per each of the two blocks in [chain1] tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(500000000), params.TxGas, nil, nil), signer, key1) @@ -1021,7 +1033,7 @@ func AcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Databa if err != nil { t.Fatal(err) } - _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 2, 10, func(i int, gen *BlockGen) { + _, _, 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 { @@ -1114,7 +1126,7 @@ func AcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Databa return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } // Insert two different chains that result in the identical state root. @@ -1131,13 +1143,14 @@ func AcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Databa // A3 // //nolint:goimports -func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -1147,13 +1160,13 @@ func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create func(db eth Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } signer := types.HomesteadSigner{} - _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { + _, _, chain1, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 10, func(i int, gen *BlockGen) { if i < 2 { // Send half the funds from addr1 to addr2 in one transaction per each of the two blocks in [chain1] tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(500000000), params.TxGas, nil, nil), signer, key1) @@ -1164,7 +1177,7 @@ func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create func(db eth if err != nil { t.Fatal(err) } - _, chain2, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 2, 10, func(i int, gen *BlockGen) { + _, _, 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 { @@ -1203,7 +1216,8 @@ func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create func(db eth blockchain.Stop() chainDB = rawdb.NewMemoryDatabase() - blockchain, err = create(chainDB, gspec, common.Hash{}) + blockDb = ethblockdb.NewMock(chainDB) + blockchain, err = create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -1282,10 +1296,10 @@ func ReprocessAcceptBlockIdenticalStateRootTest(t *testing.T, create func(db eth return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } -func GenerateChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func GenerateChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -1293,6 +1307,7 @@ func GenerateChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Databas addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -1302,13 +1317,13 @@ func GenerateChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Databas Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) require.NoError(err) t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) - _, _, _, err = GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { + _, _, _, _, err = GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), @@ -1326,7 +1341,7 @@ func GenerateChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Databas require.ErrorIs(err, dummy.ErrInsufficientBlockGas) } -func InsertChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func InsertChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -1334,6 +1349,7 @@ func InsertChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -1343,14 +1359,14 @@ func InsertChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) require.NoError(err) t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) eng := dummy.NewFakerWithMode(TestCallbacks, dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}) - _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), @@ -1370,7 +1386,7 @@ func InsertChainInvalidBlockFeeTest(t *testing.T, create func(db ethdb.Database, require.ErrorIs(err, dummy.ErrInsufficientBlockGas) } -func InsertChainValidBlockFeeTest(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { +func InsertChainValidBlockFeeTest(t *testing.T, create func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -1380,6 +1396,7 @@ func InsertChainValidBlockFeeTest(t *testing.T, create func(db ethdb.Database, g // We use two separate databases since GenerateChain commits the state roots to its underlying // database. chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -1389,7 +1406,7 @@ func InsertChainValidBlockFeeTest(t *testing.T, create func(db ethdb.Database, g Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) require.NoError(err) t.Cleanup(blockchain.Stop) @@ -1397,7 +1414,7 @@ func InsertChainValidBlockFeeTest(t *testing.T, create func(db ethdb.Database, g signer := types.LatestSigner(params.TestChainConfig) tip := big.NewInt(50000 * params.GWei) transfer := big.NewInt(10000) - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, @@ -1452,5 +1469,5 @@ func InsertChainValidBlockFeeTest(t *testing.T, create func(db ethdb.Database, g return nil } - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) } diff --git a/core/blockchain_log_test.go b/core/blockchain_log_test.go index 8a266099c6..bf28ba97d0 100644 --- a/core/blockchain_log_test.go +++ b/core/blockchain_log_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/coreth/accounts/abi" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -56,7 +57,7 @@ func TestAcceptedLogsSubscription(t *testing.T) { packedFunction, err := parsed.Pack("Call") require.NoError(err) - _, blocks, _, err := GenerateChainWithGenesis(gspec, engine, 2, 10, func(i int, b *BlockGen) { + _, _, blocks, _, err := GenerateChainWithGenesis(gspec, engine, 2, 10, func(i int, b *BlockGen) { switch i { case 0: // First, we deploy the contract @@ -74,7 +75,9 @@ func TestAcceptedLogsSubscription(t *testing.T) { }) require.NoError(err) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) require.NoError(err) defer chain.Stop() diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index cc957e1f79..cc59f1424b 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -87,11 +87,33 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body { if number == nil { return nil } - body := rawdb.ReadBody(bc.db, hash, *number) + + block := bc.blockDb.ReadBlock(*number) + var body *types.Body + if block != nil { + body = block.Body() + if block.Hash() != hash { + body = rawdb.ReadBody(bc.db, hash, *number) + } + } + if body == nil { return nil } - // Cache the found body for next time and return + + // todo: Should we not store sidechain blocks after the last accepted block? + // if so, we can not check for chainId if requesting a canonical block + // isCanonical := *number <= bc.lastAccepted.NumberU64() + // var body *types.Body + // if isCanonical { + // body = bc.blockDb.ReadBody(*number) + // } else { + // block := bc.blockDb.ReadBlock(*number) + // if block != nil && block.Hash() != hash { + // body = rawdb.ReadBody(bc.db, hash, *number) + // } + // } + bc.bodyCache.Add(hash, body) return body } @@ -101,10 +123,28 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { if bc.blockCache.Contains(hash) { return true } - if !bc.HasHeader(hash, number) { + + // todo: Should we not store sidechain blocks after the last accepted block? + // if so, we can not check for chainId if requesting a canonical block + // isCanonical := number <= bc.lastAccepted.NumberU64() + // if isCanonical { + // return bc.blockDb.HasBlock(number) + // } + + // note: a block can be in chaindb but is rejected, so we need to check + // if the block exists via ReadHeaderNumber, which stores all non-rejected + // hash->number mappings of blocks. + headerNumber := rawdb.ReadHeaderNumber(bc.db, hash) + if headerNumber == nil { return false } - return rawdb.HasBody(bc.db, hash, number) + + header := bc.blockDb.ReadHeader(number) + if header != nil && header.Hash() != hash { + return rawdb.HasHeader(bc.db, hash, number) && rawdb.HasBody(bc.db, hash, number) + } + + return header != nil } // HasFastBlock checks if a fast block is fully present in the database or not. @@ -125,10 +165,32 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { if block, ok := bc.blockCache.Get(hash); ok { return block } - block := rawdb.ReadBlock(bc.db, hash, number) + + block := bc.blockDb.ReadBlock(number) + + // the block could be in chainDb if its not found in blockdb + if block != nil && block.Hash() != hash { + block = rawdb.ReadBlock(bc.db, hash, number) + } + + // todo: Should we not store sidechain blocks after the last accepted block? + // if so, we can not check for chainId if requesting a canonical block + // Note: currently tests in blockchain_repair_test.go rely on being able to + // fetch sidechain blocks at heights at or before last accepted (see `verifyCutoff`), therefore + // GetBlock/GetHeader/GetBody currently cannot just return nil if requesting a block + // <= last accepted height if it is not in blockdb. + // if block != nil && block.Hash() != hash { + // if bc.lastAccepted != nil && number > bc.lastAccepted.NumberU64() { + // block = rawdb.ReadBlock(bc.db, hash, number) + // } else { + // block = nil + // } + // } + if block == nil { return nil } + // Cache the found block for next time and return bc.blockCache.Add(block.Hash(), block) return block @@ -181,11 +243,18 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { if number == nil { return nil } - header := bc.GetHeader(hash, *number) + header := bc.hc.blockDb.ReadHeader(*number) + if header == nil { return nil } - receipts := rawdb.ReadReceipts(bc.db, hash, *number, header.Time, bc.chainConfig) + + // if block is not in blockdb, read it from chaindb. + if header.Hash() != hash { + return rawdb.ReadReceipts(bc.db, hash, *number, header.Time, bc.chainConfig) + } + + receipts := bc.blockDb.ReadReceipts(hash, *number, header.Time, bc.chainConfig) if receipts == nil { return nil } @@ -213,7 +282,10 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo if item, exist := bc.txLookupCache.Get(hash); exist { return item.lookup, item.transaction, nil } - tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) + tx, blockHash, blockNumber, txIndex := bc.blockDb.ReadTransaction(hash) + if tx != nil && tx.Hash() != hash { + tx, blockHash, blockNumber, txIndex = rawdb.ReadTransaction(bc.db, hash) + } if tx == nil { // The transaction is already indexed, the transaction is either // not existent or not in the range of index, returning null. diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 2a48330b26..11453503c5 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -35,8 +35,10 @@ import ( "math/big" "testing" + "github.com/ava-labs/avalanchego/x/blockdb" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -528,6 +530,13 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s } defer db.Close() // Might double close, should be fine + blockDb, err := blockdb.New(blockdb.DefaultConfig().WithDir(datadir), nil) + if err != nil { + t.Fatalf("Failed to create block database: %v", err) + } + defer blockDb.Close() + ethblockDb := ethblockdb.New(blockDb, db) + // Initialize a fresh chain var ( require = require.New(t) @@ -554,7 +563,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, gspec, engine, vm.Config{}, common.Hash{}, false) + chain, err := NewBlockChain(db, ethblockDb, config, gspec, engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -565,7 +574,8 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s var sideblocks types.Blocks if tt.sidechainBlocks > 0 { genDb := rawdb.NewMemoryDatabase() - gspec.MustCommit(genDb, triedb.NewDatabase(genDb, nil)) + genBlockDb := ethblockdb.NewMock(genDb) + gspec.MustCommit(genDb, genBlockDb, triedb.NewDatabase(genDb, nil)) sideblocks, _, err = GenerateChain(gspec.Config, gspec.ToBlock(), engine, genDb, tt.sidechainBlocks, 10, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x01}) tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x01}, big.NewInt(10000), params.TxGas, big.NewInt(ap3.InitialBaseFee), nil), signer, key1) @@ -578,7 +588,8 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s } } genDb := rawdb.NewMemoryDatabase() - gspec.MustCommit(genDb, triedb.NewDatabase(genDb, nil)) + genBlockDb := ethblockdb.NewMock(genDb) + gspec.MustCommit(genDb, genBlockDb, triedb.NewDatabase(genDb, nil)) canonblocks, _, err := GenerateChain(gspec.Config, gspec.ToBlock(), engine, genDb, tt.canonicalBlocks, 10, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x02}) b.SetDifficulty(big.NewInt(1000000)) @@ -608,6 +619,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s // Pull the plug on the database, simulating a hard crash chain.triedb.Close() db.Close() + blockDb.Close() chain.stopWithoutSaving() // Start a new blockchain back up and see where the repair leads us @@ -619,8 +631,13 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s t.Fatalf("Failed to reopen persistent database: %v", err) } defer db.Close() - - newChain, err := NewBlockChain(db, config, gspec, engine, vm.Config{}, lastAcceptedHash, false) + blockDb, err = blockdb.New(blockdb.DefaultConfig().WithDir(datadir), nil) + if err != nil { + t.Fatalf("Failed to create block database: %v", err) + } + defer blockDb.Close() + ethblockDb = ethblockdb.New(blockDb, db) + newChain, err := NewBlockChain(db, ethblockDb, config, gspec, engine, vm.Config{}, lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index d17c32fec9..08301a2215 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -39,9 +39,11 @@ import ( "strings" "testing" + "github.com/ava-labs/avalanchego/x/blockdb" "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -64,6 +66,7 @@ type snapshotTestBasic struct { datadir string ancient string db ethdb.Database + blockDb ethblockdb.Database genDb ethdb.Database engine consensus.Engine gspec *Genesis @@ -91,11 +94,14 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } engine = dummy.NewFullFaker() ) - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, engine, vm.Config{}, common.Hash{}, false) + basic.datadir = datadir + basic.db = db + basic.blockDb = basic.newBlockDb(t) + chain, err := NewBlockChain(db, basic.blockDb, DefaultCacheConfigWithScheme(basic.scheme), gspec, engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("Failed to create chain: %v", err) } - genDb, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, basic.chainBlocks, 10, func(i int, b *BlockGen) {}) + genDb, _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, basic.chainBlocks, 10, func(i int, b *BlockGen) {}) // genesis as last accepted basic.lastAcceptedHash = chain.GetBlockByNumber(0).Hash() @@ -131,7 +137,6 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } // Set runtime fields - basic.datadir = datadir basic.ancient = ancient basic.db = db basic.genDb = genDb @@ -213,6 +218,7 @@ func (basic *snapshotTestBasic) dump() string { func (basic *snapshotTestBasic) teardown() { basic.db.Close() basic.genDb.Close() + basic.blockDb.Close() os.RemoveAll(basic.datadir) os.RemoveAll(basic.ancient) } @@ -231,7 +237,7 @@ func (snaptest *snapshotTest) test(t *testing.T) { // Restart the chain normally chain.Stop() - newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err := NewBlockChain(snaptest.db, snaptest.blockDb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -246,6 +252,14 @@ type crashSnapshotTest struct { snapshotTestBasic } +func (snaptest *snapshotTestBasic) newBlockDb(t *testing.T) ethblockdb.Database { + blockDb, err := blockdb.New(blockdb.DefaultConfig().WithDir(snaptest.datadir).WithSyncToDisk(false), nil) + if err != nil { + t.Fatalf("Failed to create block database: %v", err) + } + return ethblockdb.New(blockDb, snaptest.db) +} + func (snaptest *crashSnapshotTest) test(t *testing.T) { // It's hard to follow the test case, visualize the input // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) @@ -257,6 +271,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { db.Close() chain.stopWithoutSaving() chain.triedb.Close() + chain.blockDb.Close() // Start a new blockchain back up and see where the repair leads us newdb, err := rawdb.Open(rawdb.OpenOptions{ @@ -266,19 +281,22 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } + snaptest.db = newdb + newBlockDb := snaptest.newBlockDb(t) + defer newBlockDb.Close() defer newdb.Close() // The interesting thing is: instead of starting the blockchain after // the crash, we do restart twice here: one after the crash and one // after the normal stop. It's used to ensure the broken snapshot // can be detected all the time. - newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err := NewBlockChain(newdb, newBlockDb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } newchain.Stop() - newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err = NewBlockChain(newdb, newBlockDb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -317,7 +335,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { StateHistory: 32, StateScheme: snaptest.scheme, } - newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err := NewBlockChain(snaptest.db, snaptest.blockDb, cacheConfig, snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -325,7 +343,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err = NewBlockChain(snaptest.db, snaptest.blockDb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -363,7 +381,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { StateHistory: 32, StateScheme: snaptest.scheme, } - newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err := NewBlockChain(snaptest.db, snaptest.blockDb, config, snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -382,7 +400,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotWait: false, // Don't wait rebuild StateScheme: snaptest.scheme, } - tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + tmp, err := NewBlockChain(snaptest.db, snaptest.blockDb, config, snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -391,7 +409,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { tmp.triedb.Close() tmp.stopWithoutSaving() - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) + newchain, err = NewBlockChain(snaptest.db, snaptest.blockDb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash, false) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 36729b3640..4178196de8 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/coreth/core/state/pruner" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/customrawdb" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -80,6 +81,7 @@ func newGwei(n int64) *big.Int { func createBlockChain( db ethdb.Database, + blockDb ethblockdb.Database, cacheConfig *CacheConfig, gspec *Genesis, lastAcceptedHash common.Hash, @@ -87,6 +89,7 @@ func createBlockChain( // Import the chain. This runs all block validation rules. blockchain, err := NewBlockChain( db, + blockDb, cacheConfig, gspec, dummy.NewFakerWithCallbacks(TestCallbacks), @@ -98,8 +101,8 @@ func createBlockChain( } func TestArchiveBlockChain(t *testing.T) { - createArchiveBlockChain := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { - return createBlockChain(db, archiveConfig, gspec, lastAcceptedHash) + createArchiveBlockChain := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + return createBlockChain(db, blockDb, archiveConfig, gspec, lastAcceptedHash) } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { @@ -109,9 +112,10 @@ func TestArchiveBlockChain(t *testing.T) { } func TestArchiveBlockChainSnapsDisabled(t *testing.T) { - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { return createBlockChain( db, + blockDb, &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -133,8 +137,8 @@ func TestArchiveBlockChainSnapsDisabled(t *testing.T) { } func TestPruningBlockChain(t *testing.T) { - createPruningBlockChain := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { - return createBlockChain(db, pruningConfig, gspec, lastAcceptedHash) + createPruningBlockChain := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + return createBlockChain(db, blockDb, pruningConfig, gspec, lastAcceptedHash) } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { @@ -144,9 +148,10 @@ func TestPruningBlockChain(t *testing.T) { } func TestPruningBlockChainSnapsDisabled(t *testing.T) { - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { return createBlockChain( db, + blockDb, &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -176,8 +181,8 @@ type wrappedStateManager struct { func (w *wrappedStateManager) Shutdown() error { return nil } func TestPruningBlockChainUngracefulShutdown(t *testing.T) { - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { - blockchain, err := createBlockChain(db, pruningConfig, gspec, lastAcceptedHash) + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + blockchain, err := createBlockChain(db, blockDb, pruningConfig, gspec, lastAcceptedHash) if err != nil { return nil, err } @@ -195,9 +200,10 @@ func TestPruningBlockChainUngracefulShutdown(t *testing.T) { } func TestPruningBlockChainUngracefulShutdownSnapsDisabled(t *testing.T) { - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { blockchain, err := createBlockChain( db, + blockDb, &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -231,10 +237,11 @@ func TestPruningBlockChainUngracefulShutdownSnapsDisabled(t *testing.T) { func TestEnableSnapshots(t *testing.T) { // Set snapshots to be disabled the first time, and then enable them on the restart snapLimit := 0 - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { // Import the chain. This runs all block validation rules. blockchain, err := createBlockChain( db, + blockDb, &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -264,13 +271,13 @@ func TestEnableSnapshots(t *testing.T) { } func TestCorruptSnapshots(t *testing.T) { - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { // Delete the snapshot block hash and state root to ensure that if we die in between writing a snapshot // diff layer to disk at any point, we can still recover on restart. customrawdb.DeleteSnapshotBlockHash(db) rawdb.DeleteSnapshotRoot(db) - return createBlockChain(db, pruningConfig, gspec, lastAcceptedHash) + return createBlockChain(db, blockDb, pruningConfig, gspec, lastAcceptedHash) } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { @@ -280,9 +287,9 @@ func TestCorruptSnapshots(t *testing.T) { } func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { - create := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + create := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { // Import the chain. This runs all block validation rules. - blockchain, err := createBlockChain(db, pruningConfig, gspec, lastAcceptedHash) + blockchain, err := createBlockChain(db, blockDb, pruningConfig, gspec, lastAcceptedHash) if err != nil { return nil, err } @@ -308,7 +315,7 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { BloomSize: 256, } - pruner, err := pruner.NewPruner(db, prunerConfig) + pruner, err := pruner.NewPruner(db, blockDb, prunerConfig) if err != nil { return nil, fmt.Errorf("offline pruning failed (%s, %d): %w", tempDir, 256, err) } @@ -317,7 +324,7 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { return nil, fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err) } // Re-initialize the blockchain after pruning - return createBlockChain(db, pruningConfig, gspec, lastAcceptedHash) + return createBlockChain(db, blockDb, pruningConfig, gspec, lastAcceptedHash) } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { @@ -334,6 +341,7 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -343,7 +351,7 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := createBlockChain(chainDB, pruningConfig, gspec, common.Hash{}) + blockchain, err := createBlockChain(chainDB, blockDb, pruningConfig, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -351,7 +359,7 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { // This call generates a chain of 3 blocks. signer := types.HomesteadSigner{} - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 10, 10, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 10, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) @@ -372,7 +380,7 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { lastAcceptedHash := blockchain.LastConsensusAcceptedBlock().Hash() blockchain.Stop() - blockchain, err = createBlockChain(chainDB, pruningConfig, gspec, lastAcceptedHash) + blockchain, err = createBlockChain(chainDB, blockDb, pruningConfig, gspec, lastAcceptedHash) if err != nil { t.Fatal(err) } @@ -389,6 +397,7 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { // Create a node in archival mode and re-populate the trie history. blockchain, err = createBlockChain( chainDB, + blockDb, &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -424,8 +433,8 @@ func TestRepopulateMissingTries(t *testing.T) { func TestUngracefulAsyncShutdown(t *testing.T) { var ( - create = func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { - blockchain, err := createBlockChain(db, &CacheConfig{ + create = func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + blockchain, err := createBlockChain(db, blockDb, &CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieDirtyCommitTarget: 20, @@ -448,6 +457,7 @@ func TestUngracefulAsyncShutdown(t *testing.T) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) chainDB = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(chainDB) ) // Ensure that key1 has some funds in the genesis block. @@ -457,7 +467,7 @@ func TestUngracefulAsyncShutdown(t *testing.T) { Alloc: types.GenesisAlloc{addr1: {Balance: genesisBalance}}, } - blockchain, err := create(chainDB, gspec, common.Hash{}) + blockchain, err := create(chainDB, blockDb, gspec, common.Hash{}) if err != nil { t.Fatal(err) } @@ -465,7 +475,7 @@ func TestUngracefulAsyncShutdown(t *testing.T) { // This call generates a chain of 10 blocks. signer := types.HomesteadSigner{} - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 10, 10, func(i int, gen *BlockGen) { + _, _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 10, 10, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) }) @@ -498,7 +508,7 @@ func TestUngracefulAsyncShutdown(t *testing.T) { foundTxs = append(foundTxs, tx.Hash()) } } else { - // If > 3, all txs should be accessible on lookup + // If > 3, all txs should not be accessible on lookup for _, tx := range block.Transactions() { missingTxs = append(missingTxs, tx.Hash()) } @@ -547,7 +557,7 @@ func TestUngracefulAsyncShutdown(t *testing.T) { return nil } - _, newChain, restartedChain := checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) + _, newChain, restartedChain := checkBlockChainState(t, blockchain, gspec, chainDB, blockDb, create, checkState) allTxs := append(foundTxs, missingTxs...) for _, bc := range []*BlockChain{newChain, restartedChain} { @@ -617,17 +627,19 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { } engine = dummy.NewCoinbaseFaker() ) - _, forkA, _, err := GenerateChainWithGenesis(gspec, engine, c.forkA, 10, func(i int, gen *BlockGen) {}) + _, _, forkA, _, err := GenerateChainWithGenesis(gspec, engine, c.forkA, 10, func(i int, gen *BlockGen) {}) if err != nil { t.Fatal(err) } - _, forkB, _, err := GenerateChainWithGenesis(gspec, engine, c.forkB, 10, func(i int, gen *BlockGen) {}) + _, _, forkB, _, err := GenerateChainWithGenesis(gspec, engine, c.forkB, 10, func(i int, gen *BlockGen) {}) if err != nil { t.Fatal(err) } // Initialize test chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, engine, vm.Config{}, common.Hash{}, false) + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfigWithScheme(scheme), gspec, engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -696,8 +708,8 @@ func TestTxLookupBlockChain(t *testing.T) { AcceptorQueueLimit: 64, // ensure channel doesn't block TransactionHistory: 5, } - createTxLookupBlockChain := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { - return createBlockChain(db, cacheConf, gspec, lastAcceptedHash) + createTxLookupBlockChain := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + return createBlockChain(db, blockDb, cacheConf, gspec, lastAcceptedHash) } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { @@ -721,8 +733,8 @@ func TestTxLookupSkipIndexingBlockChain(t *testing.T) { TransactionHistory: 5, SkipTxIndexing: true, } - createTxLookupBlockChain := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { - return createBlockChain(db, cacheConf, gspec, lastAcceptedHash) + createTxLookupBlockChain := func(db ethdb.Database, blockDb ethblockdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { + return createBlockChain(db, blockDb, cacheConf, gspec, lastAcceptedHash) } for _, tt := range tests { t.Run(tt.Name, func(t *testing.T) { @@ -786,7 +798,7 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { } nonce := uint64(0) signer := types.HomesteadSigner{} - _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, 2, 10, func(i int, b *BlockGen) { + _, _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, 2, 10, func(i int, b *BlockGen) { fee := big.NewInt(1) if b.header.BaseFee != nil { fee = b.header.BaseFee @@ -810,7 +822,9 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { nonce++ }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfig, gspec, engine, vm.Config{ + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, engine, vm.Config{ //Debug: true, //Tracer: logger.NewJSONLogger(nil, os.Stdout), }, common.Hash{}, false) @@ -872,7 +886,7 @@ func TestDeleteThenCreate(t *testing.T) { } nonce := uint64(0) signer := types.HomesteadSigner{} - _, blocks, _, err := GenerateChainWithGenesis(gspec, engine, 2, 10, func(i int, b *BlockGen) { + _, _, blocks, _, err := GenerateChainWithGenesis(gspec, engine, 2, 10, func(i int, b *BlockGen) { fee := big.NewInt(1) if b.header.BaseFee != nil { fee = b.header.BaseFee @@ -928,7 +942,9 @@ func TestDeleteThenCreate(t *testing.T) { t.Fatal(err) } // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -988,7 +1004,7 @@ func TestTransientStorageReset(t *testing.T) { } nonce := uint64(0) signer := types.HomesteadSigner{} - _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, 1, 10, func(i int, b *BlockGen) { + _, _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, 1, 10, func(i int, b *BlockGen) { fee := big.NewInt(1) if b.header.BaseFee != nil { fee = b.header.BaseFee @@ -1014,7 +1030,9 @@ func TestTransientStorageReset(t *testing.T) { }) // Initialize the blockchain with 1153 enabled. - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfig, gspec, engine, vmConfig, common.Hash{}, false) + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, engine, vmConfig, common.Hash{}, false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1086,7 +1104,7 @@ func TestEIP3651(t *testing.T) { signer := types.LatestSigner(gspec.Config) - _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, 1, 10, func(i int, b *BlockGen) { + _, _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, 1, 10, func(i int, b *BlockGen) { b.SetCoinbase(aa) // One transaction to Coinbase txdata := &types.DynamicFeeTx{ @@ -1104,7 +1122,9 @@ func TestEIP3651(t *testing.T) { b.AddTx(tx) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfig, gspec, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, common.Hash{}, false) + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr)}, common.Hash{}, false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go index fa85b3fdc1..5d2d96f8e7 100644 --- a/core/bloom_indexer.go +++ b/core/bloom_indexer.go @@ -32,6 +32,7 @@ import ( "time" "github.com/ava-labs/coreth/core/bloombits" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/common/bitutil" "github.com/ava-labs/libevm/core/rawdb" @@ -57,14 +58,14 @@ type BloomIndexer struct { // NewBloomIndexer returns a chain indexer that generates bloom bits data for the // canonical chain for fast logs filtering. -func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *ChainIndexer { +func NewBloomIndexer(db ethdb.Database, blockDb ethblockdb.Database, size, confirms uint64) *ChainIndexer { backend := &BloomIndexer{ db: db, size: size, } table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) - return NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits") + return NewChainIndexer(db, table, blockDb, backend, size, confirms, bloomThrottling, "bloombits") } // Reset implements core.ChainIndexerBackend, starting a new bloombits index diff --git a/core/chain_indexer.go b/core/chain_indexer.go index bbd143cfe8..8492972b56 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -36,6 +36,7 @@ import ( "sync/atomic" "time" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" @@ -84,6 +85,7 @@ type ChainIndexerChain interface { type ChainIndexer struct { chainDb ethdb.Database // Chain database to index the data from indexDb ethdb.Database // Prefixed table-view of the db to write index metadata into + blockDb ethblockdb.Database // Block database to index the data from backend ChainIndexerBackend // Background processor generating the index data content children []*ChainIndexer // Child indexers to cascade chain updates to @@ -112,10 +114,11 @@ type ChainIndexer struct { // NewChainIndexer creates a new chain indexer to do background processing on // chain segments of a given size after certain number of confirmations passed. // The throttling parameter might be used to prevent database thrashing. -func NewChainIndexer(chainDb ethdb.Database, indexDb ethdb.Database, backend ChainIndexerBackend, section, confirm uint64, throttling time.Duration, kind string) *ChainIndexer { +func NewChainIndexer(chainDb ethdb.Database, indexDb ethdb.Database, blockDb ethblockdb.Database, backend ChainIndexerBackend, section, confirm uint64, throttling time.Duration, kind string) *ChainIndexer { c := &ChainIndexer{ chainDb: chainDb, indexDb: indexDb, + blockDb: blockDb, backend: backend, update: make(chan struct{}, 1), quit: make(chan chan error), @@ -411,7 +414,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com if hash == (common.Hash{}) { return common.Hash{}, fmt.Errorf("canonical block #%d unknown", number) } - header := rawdb.ReadHeader(c.chainDb, hash, number) + header := c.blockDb.ReadHeader(number) if header == nil { return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4]) } else if header.ParentHash != lastHead { diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index d6118b8222..b108da46b1 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -36,6 +36,7 @@ import ( "testing" "time" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" @@ -61,6 +62,7 @@ func TestChainIndexerWithChildren(t *testing.T) { // are randomized. func testChainIndexer(t *testing.T, count int) { db := rawdb.NewMemoryDatabase() + blockdb := ethblockdb.NewMock(db) defer db.Close() // Create a chain of indexers and ensure they all report empty @@ -71,7 +73,7 @@ func testChainIndexer(t *testing.T, count int) { confirmsReq = uint64(rand.Intn(10)) ) backends[i] = &testChainIndexBackend{t: t, processCh: make(chan uint64)} - backends[i].indexer = NewChainIndexer(db, rawdb.NewTable(db, string([]byte{byte(i)})), backends[i], sectionSize, confirmsReq, 0, fmt.Sprintf("indexer-%d", i)) + backends[i].indexer = NewChainIndexer(db, rawdb.NewTable(db, string([]byte{byte(i)})), blockdb, backends[i], sectionSize, confirmsReq, 0, fmt.Sprintf("indexer-%d", i)) if sections, _, _ := backends[i].indexer.Sections(); sections != 0 { t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, 0) @@ -107,7 +109,7 @@ func testChainIndexer(t *testing.T, count int) { if number > 0 { header.ParentHash = rawdb.ReadCanonicalHash(db, number-1) } - rawdb.WriteHeader(db, header) + blockdb.WriteBlock(types.NewBlockWithHeader(header)) rawdb.WriteCanonicalHash(db, header.Hash(), number) } // Start indexer with an already existing chain diff --git a/core/chain_makers.go b/core/chain_makers.go index a207838e73..29eb1f070b 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/coreth/consensus/misc/eip4844" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/header" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -359,16 +360,17 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse // GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize // genesis block to database first according to the provided genesis specification // then generate chain on top. -func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gap uint64, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts, error) { +func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gap uint64, gen func(int, *BlockGen)) (ethdb.Database, ethblockdb.Database, []*types.Block, []types.Receipts, error) { db := rawdb.NewMemoryDatabase() triedb := triedb.NewDatabase(db, triedb.HashDefaults) defer triedb.Close() - _, err := genesis.Commit(db, triedb) + blockDb := ethblockdb.NewMock(db) + _, err := genesis.Commit(db, blockDb, triedb) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } blocks, receipts, err := GenerateChain(genesis.Config, genesis.ToBlock(), engine, db, n, gap, gen) - return db, blocks, receipts, err + return db, blockDb, blocks, receipts, err } func (cm *chainMaker) makeHeader(parent *types.Block, gap uint64, state *state.StateDB, engine consensus.Engine) *types.Header { diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 2bae562610..840aba0df7 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -33,6 +33,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" @@ -43,14 +44,16 @@ import ( func ExampleGenerateChain() { var ( - key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - addr1 = crypto.PubkeyToAddress(key1.PublicKey) - addr2 = crypto.PubkeyToAddress(key2.PublicKey) - addr3 = crypto.PubkeyToAddress(key3.PublicKey) - db = rawdb.NewMemoryDatabase() - genDb = rawdb.NewMemoryDatabase() + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + genDb = rawdb.NewMemoryDatabase() + genBlockDb = ethblockdb.NewMock(genDb) ) // Ensure that key1 has some funds in the genesis block. @@ -58,7 +61,7 @@ func ExampleGenerateChain() { Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, } - genesis := gspec.MustCommit(genDb, triedb.NewDatabase(genDb, triedb.HashDefaults)) + genesis := gspec.MustCommit(genDb, genBlockDb, triedb.NewDatabase(genDb, triedb.HashDefaults)) // This call generates a chain of 3 blocks. The function runs for // each block and adds different features to gen based on the @@ -85,7 +88,7 @@ func ExampleGenerateChain() { } // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) + blockchain, _ := NewBlockChain(db, blockDb, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) defer blockchain.Stop() if i, err := blockchain.InsertChain(chain); err != nil { diff --git a/core/coretest/test_indices.go b/core/coretest/test_indices.go index 7549296911..c32dea3b6a 100644 --- a/core/coretest/test_indices.go +++ b/core/coretest/test_indices.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/ethdb" "github.com/stretchr/testify/assert" @@ -18,7 +19,7 @@ import ( // [indexedFrom] is the block number from which the transactions should be indexed. // [indexedTo] is the block number to which the transactions should be indexed. // [head] is the block number of the head block. -func CheckTxIndices(t *testing.T, expectedTail *uint64, indexedFrom uint64, indexedTo uint64, head uint64, db ethdb.Database, allowNilBlocks bool) { +func CheckTxIndices(t *testing.T, expectedTail *uint64, indexedFrom uint64, indexedTo uint64, head uint64, db ethdb.Database, blkDb ethblockdb.Database, allowNilBlocks bool) { if expectedTail == nil { require.Nil(t, rawdb.ReadTxIndexTail(db)) } else { @@ -34,7 +35,7 @@ func CheckTxIndices(t *testing.T, expectedTail *uint64, indexedFrom uint64, inde } for i := uint64(0); i <= head; i++ { - block := rawdb.ReadBlock(db, rawdb.ReadCanonicalHash(db, i), i) + block := blkDb.ReadBlock(i) if block == nil && allowNilBlocks { continue } diff --git a/core/genesis.go b/core/genesis.go index 8970ac82d8..49809100ea 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -36,6 +36,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/coreth/triedb/pathdb" "github.com/ava-labs/libevm/common" @@ -125,7 +126,7 @@ func (e *GenesisMismatchError) Error() string { // specify a fork block below the local head block). In case of a conflict, the // error is a *params.ConfigCompatError and the new, unwritten config is returned. func SetupGenesisBlock( - db ethdb.Database, triedb *triedb.Database, genesis *Genesis, lastAcceptedHash common.Hash, skipChainConfigCheckCompatible bool, + db ethdb.Database, triedb *triedb.Database, blockDb ethblockdb.Database, genesis *Genesis, lastAcceptedHash common.Hash, skipChainConfigCheckCompatible bool, ) (*params.ChainConfig, common.Hash, error) { if genesis == nil { return nil, common.Hash{}, ErrNoGenesis @@ -137,7 +138,7 @@ func SetupGenesisBlock( stored := rawdb.ReadCanonicalHash(db, 0) if (stored == common.Hash{}) { log.Info("Writing genesis to database") - block, err := genesis.Commit(db, triedb) + block, err := genesis.Commit(db, blockDb, triedb) if err != nil { return genesis.Config, common.Hash{}, err } @@ -147,14 +148,14 @@ func SetupGenesisBlock( // state database is not initialized yet. It can happen that the node // is initialized with an external ancient store. Commit genesis state // in this case. - header := rawdb.ReadHeader(db, stored, 0) + header := blockDb.ReadHeader(0) if header.Root != types.EmptyRootHash && !triedb.Initialized(header.Root) { // Ensure the stored genesis matches with the given one. hash := genesis.ToBlock().Hash() if hash != stored { return genesis.Config, common.Hash{}, &GenesisMismatchError{stored, hash} } - _, err := genesis.Commit(db, triedb) + _, err := genesis.Commit(db, blockDb, triedb) return genesis.Config, common.Hash{}, err } // Check whether the genesis block is already written. @@ -182,7 +183,7 @@ func SetupGenesisBlock( // we use last accepted block for cfg compatibility check. Note this allows // the node to continue if it previously halted due to attempting to process blocks with // an incorrect chain config. - lastBlock := ReadBlockByHash(db, lastAcceptedHash) + lastBlock := ReadBlockByHash(db, blockDb, lastAcceptedHash) // this should never happen, but we check anyway // when we start syncing from scratch, the last accepted block // will be genesis block @@ -313,7 +314,7 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo // Commit writes the block and state of a genesis specification to the database. // The block is committed as the canonical head block. -func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Block, error) { +func (g *Genesis) Commit(db ethdb.Database, blockDb ethblockdb.Database, triedb *triedb.Database) (*types.Block, error) { block := g.toBlock(db, triedb) if block.Number().Sign() != 0 { return nil, errors.New("can't commit genesis block with number > 0") @@ -325,7 +326,8 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo if err := config.CheckConfigForkOrder(); err != nil { return nil, err } - rawdb.WriteBlock(db, block) + log.Info("Writing genesis block with hash", "hash", block.Hash()) + blockDb.WriteBlock(block) rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) rawdb.WriteHeadBlockHash(db, block.Hash()) @@ -336,8 +338,8 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo // MustCommit writes the genesis block and state to db, panicking on error. // The block is committed as the canonical head block. -func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.Block { - block, err := g.Commit(db, triedb) +func (g *Genesis) MustCommit(db ethdb.Database, blockDb ethblockdb.Database, triedb *triedb.Database) *types.Block { + block, err := g.Commit(db, blockDb, triedb) if err != nil { panic(err) } @@ -351,14 +353,14 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big Alloc: types.GenesisAlloc{addr: {Balance: balance}}, BaseFee: big.NewInt(ap3.InitialBaseFee), } - return g.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) + return g.MustCommit(db, ethblockdb.NewMock(db), triedb.NewDatabase(db, triedb.HashDefaults)) } // ReadBlockByHash reads the block with the given hash from the database. -func ReadBlockByHash(db ethdb.Reader, hash common.Hash) *types.Block { +func ReadBlockByHash(db ethdb.Reader, blockDb ethblockdb.Database, hash common.Hash) *types.Block { blockNumber := rawdb.ReadHeaderNumber(db, hash) if blockNumber == nil { return nil } - return rawdb.ReadBlock(db, hash, *blockNumber) + return blockDb.ReadBlock(*blockNumber) } diff --git a/core/genesis_extra_test.go b/core/genesis_extra_test.go index 323af111f3..11910f8add 100644 --- a/core/genesis_extra_test.go +++ b/core/genesis_extra_test.go @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/params/extras" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/utils" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -73,9 +74,10 @@ func TestGenesisEthUpgrades(t *testing.T) { ) tdb := triedb.NewDatabase(db, triedb.HashDefaults) + blockDb := ethblockdb.NewMock(db) config := *preEthUpgrades // Set this up once, just to get the genesis hash - _, genHash, err := SetupGenesisBlock(db, tdb, &Genesis{Config: &config}, common.Hash{}, false) + _, genHash, err := SetupGenesisBlock(db, tdb, blockDb, &Genesis{Config: &config}, common.Hash{}, false) require.NoError(t, err) // Write the configuration back to the db as it would be in prior versions @@ -91,11 +93,11 @@ func TestGenesisEthUpgrades(t *testing.T) { }, nil, nil, nil, nil, ) - rawdb.WriteBlock(db, block) + blockDb.WriteBlock(block) // We should still be able to re-initialize config = *preEthUpgrades require.NoError(t, params.SetEthUpgrades(&config)) // New versions will set additional fields eg, LondonBlock - _, _, err = SetupGenesisBlock(db, tdb, &Genesis{Config: &config}, block.Hash(), false) + _, _, err = SetupGenesisBlock(db, tdb, blockDb, &Genesis{Config: &config}, block.Hash(), false) require.NoError(t, err) } diff --git a/core/genesis_test.go b/core/genesis_test.go index e89928eb5b..4d71b3536e 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/params/extras" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/coreth/precompile/contracts/warp" "github.com/ava-labs/coreth/triedb/pathdb" @@ -53,8 +54,8 @@ import ( "github.com/stretchr/testify/require" ) -func setupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, lastAcceptedHash common.Hash) (*params.ChainConfig, common.Hash, error) { - return SetupGenesisBlock(db, triedb, genesis, lastAcceptedHash, false) +func setupGenesisBlock(db ethdb.Database, triedb *triedb.Database, blockDb ethblockdb.Database, genesis *Genesis, lastAcceptedHash common.Hash) (*params.ChainConfig, common.Hash, error) { + return SetupGenesisBlock(db, triedb, blockDb, genesis, lastAcceptedHash, false) } func TestGenesisBlockForTesting(t *testing.T) { @@ -89,62 +90,62 @@ func testSetupGenesis(t *testing.T, scheme string) { oldcustomg.Config = &rollbackApricotPhase1Config tests := []struct { name string - fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error) + fn func(ethdb.Database, ethblockdb.Database) (*params.ChainConfig, common.Hash, error) wantConfig *params.ChainConfig wantHash common.Hash wantErr error }{ { name: "genesis without ChainConfig", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - return setupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), new(Genesis), common.Hash{}) + fn: func(db ethdb.Database, blockDb ethblockdb.Database) (*params.ChainConfig, common.Hash, error) { + return setupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), blockDb, new(Genesis), common.Hash{}) }, wantErr: errGenesisNoConfig, wantConfig: nil, }, { name: "no block in DB, genesis == nil", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - return setupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), nil, common.Hash{}) + fn: func(db ethdb.Database, blockDb ethblockdb.Database) (*params.ChainConfig, common.Hash, error) { + return setupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), blockDb, nil, common.Hash{}) }, wantErr: ErrNoGenesis, wantConfig: nil, }, { name: "custom block in DB, genesis == nil", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + fn: func(db ethdb.Database, blockDb ethblockdb.Database) (*params.ChainConfig, common.Hash, error) { tdb := triedb.NewDatabase(db, newDbConfig(scheme)) - customg.Commit(db, tdb) - return setupGenesisBlock(db, tdb, nil, common.Hash{}) + customg.Commit(db, blockDb, tdb) + return setupGenesisBlock(db, tdb, blockDb, nil, common.Hash{}) }, wantErr: ErrNoGenesis, wantConfig: nil, }, { name: "compatible config in DB", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + fn: func(db ethdb.Database, blockDb ethblockdb.Database) (*params.ChainConfig, common.Hash, error) { tdb := triedb.NewDatabase(db, newDbConfig(scheme)) - oldcustomg.Commit(db, tdb) - return setupGenesisBlock(db, tdb, &customg, customghash) + oldcustomg.Commit(db, blockDb, tdb) + return setupGenesisBlock(db, tdb, blockDb, &customg, customghash) }, wantHash: customghash, wantConfig: customg.Config, }, { name: "incompatible config for avalanche fork in DB", - fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + fn: func(db ethdb.Database, blockDb ethblockdb.Database) (*params.ChainConfig, common.Hash, error) { // Commit the 'old' genesis block with ApricotPhase1 transition at 90. // Advance to block #4, past the ApricotPhase1 transition block of customg. tdb := triedb.NewDatabase(db, newDbConfig(scheme)) - genesis, err := oldcustomg.Commit(db, tdb) + genesis, err := oldcustomg.Commit(db, blockDb, tdb) if err != nil { t.Fatal(err) } - bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, dummy.NewFullFaker(), vm.Config{}, genesis.Hash(), false) + bc, _ := NewBlockChain(db, blockDb, DefaultCacheConfigWithScheme(scheme), &oldcustomg, dummy.NewFullFaker(), vm.Config{}, genesis.Hash(), false) defer bc.Stop() - _, blocks, _, err := GenerateChainWithGenesis(&oldcustomg, dummy.NewFullFaker(), 4, 25, nil) + _, _, blocks, _, err := GenerateChainWithGenesis(&oldcustomg, dummy.NewFullFaker(), 4, 25, nil) if err != nil { t.Fatal(err) } @@ -157,7 +158,7 @@ func testSetupGenesis(t *testing.T, scheme string) { } // This should return a compatibility error. - return setupGenesisBlock(db, tdb, &customg, bc.lastAccepted.Hash()) + return setupGenesisBlock(db, tdb, blockDb, &customg, bc.lastAccepted.Hash()) }, wantHash: customghash, wantConfig: customg.Config, @@ -173,7 +174,8 @@ func testSetupGenesis(t *testing.T, scheme string) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { db := rawdb.NewMemoryDatabase() - config, hash, err := test.fn(db) + blockDb := ethblockdb.NewMock(db) + config, hash, err := test.fn(db, blockDb) // Check the return values. if !reflect.DeepEqual(err, test.wantErr) { spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true} @@ -186,7 +188,7 @@ func testSetupGenesis(t *testing.T, scheme string) { t.Errorf("returned hash %s, want %s", hash.Hex(), test.wantHash.Hex()) } else if err == nil { // Check database content. - stored := rawdb.ReadBlock(db, test.wantHash, 0) + stored := blockDb.ReadBlock(0) if stored.Hash() != test.wantHash { t.Errorf("block in DB has hash %s, want %s", stored.Hash(), test.wantHash) } @@ -204,11 +206,12 @@ func TestNetworkUpgradeBetweenHeadAndAcceptedBlock(t *testing.T) { {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, }, } - bc, _ := NewBlockChain(db, DefaultCacheConfig, &customg, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false) + blockDb := ethblockdb.NewMock(db) + bc, _ := NewBlockChain(db, blockDb, DefaultCacheConfig, &customg, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false) defer bc.Stop() // Advance header to block #4, past the ApricotPhase2 timestamp. - _, blocks, _, _ := GenerateChainWithGenesis(&customg, dummy.NewFullFaker(), 4, 25, nil) + _, _, blocks, _, _ := GenerateChainWithGenesis(&customg, dummy.NewFullFaker(), 4, 25, nil) require := require.New(t) _, err := bc.InsertChain(blocks) @@ -237,7 +240,7 @@ func TestNetworkUpgradeBetweenHeadAndAcceptedBlock(t *testing.T) { require.Less(bc.lastAccepted.Time(), *apricotPhase2Timestamp) // This should not return any error since the last accepted block is before the activation block. - config, _, err := setupGenesisBlock(db, triedb.NewDatabase(db, nil), &activatedGenesis, bc.lastAccepted.Hash()) + config, _, err := setupGenesisBlock(db, triedb.NewDatabase(db, nil), blockDb, &activatedGenesis, bc.lastAccepted.Hash()) require.NoError(err) if !reflect.DeepEqual(config, activatedGenesis.Config) { t.Errorf("returned %v\nwant %v", config, activatedGenesis.Config) @@ -256,9 +259,10 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) { db := rawdb.NewMemoryDatabase() trieDB := triedb.NewDatabase(db, triedb.HashDefaults) - genesisBlock := genesis.MustCommit(db, trieDB) + blockDb := ethblockdb.NewMock(db) + genesisBlock := genesis.MustCommit(db, blockDb, trieDB) - _, _, err := SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false) + _, _, err := SetupGenesisBlock(db, trieDB, blockDb, genesis, genesisBlock.Hash(), false) require.NoError(err) params.GetExtra(genesis.Config).UpgradeConfig.PrecompileUpgrades = []extras.PrecompileUpgrade{ @@ -266,7 +270,7 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) { Config: warp.NewConfig(utils.NewUint64(51), 0, false), }, } - _, _, err = SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false) + _, _, err = SetupGenesisBlock(db, trieDB, blockDb, genesis, genesisBlock.Hash(), false) require.NoError(err) timestamp := uint64(100) @@ -277,11 +281,11 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) { Extra: nil, Time: timestamp, }, nil, nil, nil, trie.NewStackTrie(nil)) - rawdb.WriteBlock(db, lastAcceptedBlock) + blockDb.WriteBlock(lastAcceptedBlock) // Attempt restart after the chain has advanced past the activation of the precompile upgrade. // This tests a regression where the UpgradeConfig would not be written to disk correctly. - _, _, err = SetupGenesisBlock(db, trieDB, genesis, lastAcceptedBlock.Hash(), false) + _, _, err = SetupGenesisBlock(db, trieDB, blockDb, genesis, lastAcceptedBlock.Hash(), false) require.NoError(err) } @@ -329,8 +333,9 @@ func TestVerkleGenesisCommit(t *testing.T) { } db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) triedb := triedb.NewDatabase(db, &triedb.Config{IsVerkle: true, DBOverride: pathdb.Defaults.BackendConstructor}) - block := genesis.MustCommit(db, triedb) + block := genesis.MustCommit(db, blockDb, triedb) if !bytes.Equal(block.Root().Bytes(), expected) { t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) } diff --git a/core/headerchain.go b/core/headerchain.go index e39360d4b5..5d1d388920 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -36,6 +36,7 @@ import ( "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/common/lru" "github.com/ava-labs/libevm/core/rawdb" @@ -66,6 +67,7 @@ type HeaderChain struct { config *params.ChainConfig chainDb ethdb.Database + blockDb ethblockdb.Database genesisHeader *types.Header currentHeader atomic.Value // Current head of the header chain (may be above the block chain!) @@ -81,7 +83,7 @@ type HeaderChain struct { // NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points // to the parent's interrupt semaphore. -func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, cacheConfig *CacheConfig, engine consensus.Engine) (*HeaderChain, error) { +func NewHeaderChain(chainDb ethdb.Database, blockDb ethblockdb.Database, config *params.ChainConfig, cacheConfig *CacheConfig, engine consensus.Engine) (*HeaderChain, error) { acceptedNumberCache := NewFIFOCache[uint64, *types.Header](cacheConfig.AcceptedCacheSize) // Seed a fast but crypto originating random generator @@ -93,6 +95,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, cacheCon hc := &HeaderChain{ config: config, chainDb: chainDb, + blockDb: blockDb, headerCache: lru.NewCache[common.Hash, *types.Header](headerCacheLimit), numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit), acceptedNumberCache: acceptedNumberCache, @@ -136,10 +139,16 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header if header, ok := hc.headerCache.Get(hash); ok { return header } - header := rawdb.ReadHeader(hc.chainDb, hash, number) + header := hc.blockDb.ReadHeader(number) + + if header != nil && header.Hash() != hash { + header = rawdb.ReadHeader(hc.chainDb, hash, number) + } + if header == nil { return nil } + // Cache the found header for next time and return hc.headerCache.Add(hash, header) return header @@ -162,7 +171,15 @@ func (hc *HeaderChain) HasHeader(hash common.Hash, number uint64) bool { if hc.numberCache.Contains(hash) || hc.headerCache.Contains(hash) { return true } - return rawdb.HasHeader(hc.chainDb, hash, number) + + header := hc.blockDb.ReadHeader(number) + if header != nil && header.Hash() != hash { + return rawdb.HasHeader(hc.chainDb, hash, number) + } + if header == nil { + return false + } + return true } // GetHeaderByNumber retrieves a block header from the database by number, diff --git a/core/headerchain_test.go b/core/headerchain_test.go index dadf28f7f2..b3ccf39b3c 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -36,6 +36,7 @@ import ( "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -75,14 +76,15 @@ func testInsert(t *testing.T, bc *BlockChain, chain []*types.Block, wantErr erro // This test checks status reporting of InsertHeaderChain. func TestHeaderInsertion(t *testing.T) { var ( - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + gspec = &Genesis{ BaseFee: big.NewInt(ap3.InitialBaseFee), Config: params.TestChainConfig, } ) genesis := gspec.ToBlock() - chain, err := NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) + chain, err := NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) if err != nil { t.Fatal(err) } diff --git a/core/rlp_test.go b/core/rlp_test.go index f1e248f2e3..3f36f6c27b 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -56,7 +56,7 @@ func getBlock(transactions int, uncles int, dataSize int) *types.Block { } ) // We need to generate as many blocks +1 as uncles - _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, uncles+1, 10, + _, _, blocks, _, _ := GenerateChainWithGenesis(gspec, engine, uncles+1, 10, func(n int, b *BlockGen) { if n == uncles { // Add transactions and stuff on the last block diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 43fc954a4f..aebf2eb7c8 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -40,6 +40,7 @@ import ( "github.com/ava-labs/coreth/core/state/snapshot" "github.com/ava-labs/coreth/plugin/evm/customrawdb" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" @@ -88,16 +89,37 @@ type Pruner struct { config Config chainHeader *types.Header db ethdb.Database + blockDb ethblockdb.Database stateBloom *stateBloom snaptree *snapshot.Tree } +func ReadHeadBlock(db ethdb.Database, blockDb ethblockdb.Database) *types.Block { + headBlockHash := rawdb.ReadHeadBlockHash(db) + if headBlockHash == (common.Hash{}) { + return nil + } + headBlockNumber := rawdb.ReadHeaderNumber(db, headBlockHash) + if headBlockNumber == nil { + return nil + } + + block := blockDb.ReadBlock(*headBlockNumber) + + // if head block is not in blockdb, it could be a sidechain block so check chaindb + if block == nil || block.Hash() != headBlockHash { + return rawdb.ReadHeadBlock(db) + } + return block +} + // NewPruner creates the pruner instance. -func NewPruner(db ethdb.Database, config Config) (*Pruner, error) { - headBlock := rawdb.ReadHeadBlock(db) +func NewPruner(db ethdb.Database, blockDb ethblockdb.Database, config Config) (*Pruner, error) { + headBlock := ReadHeadBlock(db, blockDb) if headBlock == nil { return nil, errors.New("failed to load head block") } + // Offline pruning is only supported in legacy hash based scheme. triedb := triedb.NewDatabase(db, triedb.HashDefaults) @@ -128,6 +150,7 @@ func NewPruner(db ethdb.Database, config Config) (*Pruner, error) { config: config, chainHeader: headBlock.Header(), db: db, + blockDb: blockDb, stateBloom: stateBloom, snaptree: snaptree, }, nil @@ -268,7 +291,7 @@ func (p *Pruner) Prune(root common.Hash) error { return err } if stateBloomRoot != (common.Hash{}) { - return RecoverPruning(p.config.Datadir, p.db) + return RecoverPruning(p.config.Datadir, p.db, p.blockDb) } // If the target state root is not specified, return a fatal error. @@ -292,7 +315,7 @@ func (p *Pruner) Prune(root common.Hash) error { } // Traverse the genesis, put all genesis state entries into the // bloom filter too. - if err := extractGenesis(p.db, p.stateBloom); err != nil { + if err := extractGenesis(p.db, p.blockDb, p.stateBloom); err != nil { return err } filterName := bloomFilterName(p.config.Datadir, root) @@ -312,7 +335,7 @@ func (p *Pruner) Prune(root common.Hash) error { // pruning can be resumed. What's more if the bloom filter is constructed, the // pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left // in the disk. -func RecoverPruning(datadir string, db ethdb.Database) error { +func RecoverPruning(datadir string, db ethdb.Database, blockDb ethblockdb.Database) error { stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir) if err != nil { return err @@ -320,7 +343,7 @@ func RecoverPruning(datadir string, db ethdb.Database) error { if stateBloomPath == "" { return nil // nothing to recover } - headBlock := rawdb.ReadHeadBlock(db) + headBlock := ReadHeadBlock(db, blockDb) if headBlock == nil { return errors.New("failed to load head block") } @@ -341,12 +364,12 @@ func RecoverPruning(datadir string, db ethdb.Database) error { // extractGenesis loads the genesis state and commits all the state entries // into the given bloomfilter. -func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { +func extractGenesis(db ethdb.Database, blockDb ethblockdb.Database, stateBloom *stateBloom) error { genesisHash := rawdb.ReadCanonicalHash(db, 0) if genesisHash == (common.Hash{}) { return errors.New("missing genesis hash") } - genesis := rawdb.ReadBlock(db, genesisHash, 0) + genesis := blockDb.ReadBlock(0) if genesis == nil { return errors.New("missing genesis block") } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 4e18b4ce81..9e0634765f 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/params/extras" "github.com/ava-labs/coreth/plugin/evm/customtypes" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" customheader "github.com/ava-labs/coreth/plugin/evm/header" "github.com/ava-labs/coreth/plugin/evm/upgrade/acp176" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap1" @@ -116,8 +117,9 @@ func TestStateProcessorErrors(t *testing.T) { { // Tests against a 'recent' chain definition var ( - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + gspec = &Genesis{ Config: config, Timestamp: uint64(upgrade.InitiallyActiveTime.Unix()), Alloc: types.GenesisAlloc{ @@ -129,7 +131,7 @@ func TestStateProcessorErrors(t *testing.T) { GasLimit: cortina.GasLimit, } // FullFaker used to skip header verification that enforces no blobs. - blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false) + blockchain, _ = NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false) tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} ) @@ -265,8 +267,9 @@ func TestStateProcessorErrors(t *testing.T) { // ErrTxTypeNotSupported, For this, we need an older chain { var ( - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + gspec = &Genesis{ Config: params.WithExtra( ¶ms.ChainConfig{ ChainID: big.NewInt(1), @@ -295,7 +298,7 @@ func TestStateProcessorErrors(t *testing.T) { }, GasLimit: ap1.GasLimit, } - blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) + blockchain, _ = NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) ) defer blockchain.Stop() for i, tt := range []struct { @@ -323,8 +326,9 @@ func TestStateProcessorErrors(t *testing.T) { // ErrSenderNoEOA, for this we need the sender to have contract code { var ( - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + gspec = &Genesis{ Config: config, Alloc: types.GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{ @@ -335,7 +339,7 @@ func TestStateProcessorErrors(t *testing.T) { }, GasLimit: cortina.GasLimit, } - blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) + blockchain, _ = NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) ) defer blockchain.Stop() for i, tt := range []struct { diff --git a/core/state_transition_test.go b/core/state_transition_test.go index 2291b913ab..a657b49b84 100644 --- a/core/state_transition_test.go +++ b/core/state_transition_test.go @@ -36,6 +36,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/nativeasset" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap0" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap1" "github.com/ava-labs/libevm/common" @@ -98,8 +99,9 @@ func executeStateTransitionTest(t *testing.T, st stateTransitionTest) { require.Equal(len(st.txs), len(st.gasUsed), "length of gas used must match length of txs") var ( - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + gspec = &Genesis{ Config: st.config, Alloc: types.GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{ @@ -111,7 +113,7 @@ func executeStateTransitionTest(t *testing.T, st stateTransitionTest) { } genesis = gspec.ToBlock() engine = dummy.NewFaker() - blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) + blockchain, _ = NewBlockChain(db, blockDb, DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) ) defer blockchain.Stop() diff --git a/core/txindexer.go b/core/txindexer.go index 6eae79f2e7..06b18ac78e 100644 --- a/core/txindexer.go +++ b/core/txindexer.go @@ -115,7 +115,7 @@ func (indexer *txIndexer) run(tail *uint64, head uint64, stop chan struct{}, don if head-indexer.limit+1 >= tailValue { // Unindex a part of stale indices and forward index tail to HEAD-limit - rawdb.UnindexTransactions(indexer.db, tailValue, head-indexer.limit+1, stop, false) + indexer.chain.blockDb.UnindexTransactions(tailValue, head-indexer.limit+1, stop, nil, false) } } diff --git a/core/txindexer_test.go b/core/txindexer_test.go index 30a26e171c..4e7b4fc43a 100644 --- a/core/txindexer_test.go +++ b/core/txindexer_test.go @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core/coretest" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" @@ -60,7 +61,7 @@ func TestTransactionIndices(t *testing.T) { } signer = types.LatestSigner(gspec.Config) ) - genDb, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFakerWithCallbacks(TestCallbacks), 128, 10, func(i int, block *BlockGen) { + genDb, _, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFakerWithCallbacks(TestCallbacks), 128, 10, func(i int, block *BlockGen) { tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) require.NoError(err) block.AddTx(tx) @@ -89,7 +90,8 @@ func TestTransactionIndices(t *testing.T) { // Init block chain and check all needed indices has been indexed. chainDB := rawdb.NewMemoryDatabase() - chain, err := createBlockChain(chainDB, conf, gspec, common.Hash{}) + blockDb := ethblockdb.NewMock(chainDB) + chain, err := createBlockChain(chainDB, blockDb, conf, gspec, common.Hash{}) require.NoError(err) _, err = chain.InsertChain(blocks) @@ -104,7 +106,7 @@ func TestTransactionIndices(t *testing.T) { lastAcceptedBlock := blocks[len(blocks)-1] require.Equal(lastAcceptedBlock.Hash(), chain.CurrentHeader().Hash()) - coretest.CheckTxIndices(t, nil, 0, lastAcceptedBlock.NumberU64(), lastAcceptedBlock.NumberU64(), chain.db, false) // check all indices has been indexed + coretest.CheckTxIndices(t, nil, 0, lastAcceptedBlock.NumberU64(), lastAcceptedBlock.NumberU64(), chain.db, blockDb, false) // check all indices has been indexed chain.Stop() // Reconstruct a block chain which only reserves limited tx indices @@ -120,7 +122,7 @@ func TestTransactionIndices(t *testing.T) { t.Run(fmt.Sprintf("test-%d, limit: %d", i+1, l), func(t *testing.T) { conf.TransactionHistory = l - chain, err := createBlockChain(chainDB, conf, gspec, lastAcceptedBlock.Hash()) + chain, err := createBlockChain(chainDB, blockDb, conf, gspec, lastAcceptedBlock.Hash()) require.NoError(err) tail := getTail(l, lastAcceptedBlock.NumberU64()) @@ -129,7 +131,7 @@ func TestTransactionIndices(t *testing.T) { indexedFrom = *tail } // check if startup indices are correct - coretest.CheckTxIndices(t, tail, indexedFrom, lastAcceptedBlock.NumberU64(), lastAcceptedBlock.NumberU64(), chain.db, false) + coretest.CheckTxIndices(t, tail, indexedFrom, lastAcceptedBlock.NumberU64(), lastAcceptedBlock.NumberU64(), chain.db, blockDb, false) newBlks := blocks2[i : i+1] _, err = chain.InsertChain(newBlks) // Feed chain a higher block to trigger indices updater. @@ -146,7 +148,7 @@ func TestTransactionIndices(t *testing.T) { indexedFrom = *tail } // check if indices are updated correctly - coretest.CheckTxIndices(t, tail, indexedFrom, lastAcceptedBlock.NumberU64(), lastAcceptedBlock.NumberU64(), chain.db, false) + coretest.CheckTxIndices(t, tail, indexedFrom, lastAcceptedBlock.NumberU64(), lastAcceptedBlock.NumberU64(), chain.db, blockDb, false) chain.Stop() }) } @@ -180,7 +182,7 @@ func TestTransactionSkipIndexing(t *testing.T) { } signer = types.LatestSigner(gspec.Config) ) - genDb, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFakerWithCallbacks(TestCallbacks), 5, 10, func(i int, block *BlockGen) { + genDb, _, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFakerWithCallbacks(TestCallbacks), 5, 10, func(i int, block *BlockGen) { tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) require.NoError(err) block.AddTx(tx) @@ -210,10 +212,11 @@ func TestTransactionSkipIndexing(t *testing.T) { // test1: Init block chain and check all indices has been skipped. chainDB := rawdb.NewMemoryDatabase() - chain, err := createAndInsertChain(chainDB, conf, gspec, blocks, common.Hash{}, + blockDb := ethblockdb.NewMock(chainDB) + chain, err := createAndInsertChain(chainDB, blockDb, conf, gspec, blocks, common.Hash{}, func(b *types.Block) { bNumber := b.NumberU64() - coretest.CheckTxIndices(t, nil, bNumber+1, bNumber+1, bNumber, chainDB, false) // check all indices has been skipped + coretest.CheckTxIndices(t, nil, bNumber+1, bNumber+1, bNumber, chainDB, blockDb, false) // check all indices has been skipped }) require.NoError(err) chain.Stop() @@ -221,11 +224,12 @@ func TestTransactionSkipIndexing(t *testing.T) { // test2: specify lookuplimit with tx index skipping enabled. Blocks should not be indexed but tail should be updated. conf.TransactionHistory = 2 chainDB = rawdb.NewMemoryDatabase() - chain, err = createAndInsertChain(chainDB, conf, gspec, blocks, common.Hash{}, + blockDb = ethblockdb.NewMock(chainDB) + chain, err = createAndInsertChain(chainDB, blockDb, conf, gspec, blocks, common.Hash{}, func(b *types.Block) { bNumber := b.NumberU64() tail := bNumber - conf.TransactionHistory + 1 - coretest.CheckTxIndices(t, &tail, bNumber+1, bNumber+1, bNumber, chainDB, false) // check all indices has been skipped + coretest.CheckTxIndices(t, &tail, bNumber+1, bNumber+1, bNumber, chainDB, blockDb, false) // check all indices has been skipped }) require.NoError(err) chain.Stop() @@ -234,10 +238,11 @@ func TestTransactionSkipIndexing(t *testing.T) { conf.TransactionHistory = 0 conf.SkipTxIndexing = false chainDB = rawdb.NewMemoryDatabase() - chain, err = createAndInsertChain(chainDB, conf, gspec, blocks, common.Hash{}, + blockDb = ethblockdb.NewMock(chainDB) + chain, err = createAndInsertChain(chainDB, blockDb, conf, gspec, blocks, common.Hash{}, func(b *types.Block) { bNumber := b.NumberU64() - coretest.CheckTxIndices(t, nil, 0, bNumber, bNumber, chainDB, false) // check all indices has been indexed + coretest.CheckTxIndices(t, nil, 0, bNumber, bNumber, chainDB, blockDb, false) // check all indices has been indexed }) require.NoError(err) chain.Stop() @@ -246,18 +251,18 @@ func TestTransactionSkipIndexing(t *testing.T) { // and old indices are removed up to the tail, but [tail, current) indices are still there. conf.TransactionHistory = 2 conf.SkipTxIndexing = true - chain, err = createAndInsertChain(chainDB, conf, gspec, blocks2[0:1], chain.CurrentHeader().Hash(), + chain, err = createAndInsertChain(chainDB, blockDb, conf, gspec, blocks2[0:1], chain.CurrentHeader().Hash(), func(b *types.Block) { bNumber := b.NumberU64() tail := bNumber - conf.TransactionHistory + 1 - coretest.CheckTxIndices(t, &tail, tail, bNumber-1, bNumber, chainDB, false) + coretest.CheckTxIndices(t, &tail, tail, bNumber-1, bNumber, chainDB, blockDb, false) }) require.NoError(err) chain.Stop() } -func createAndInsertChain(db ethdb.Database, cacheConfig *CacheConfig, gspec *Genesis, blocks types.Blocks, lastAcceptedHash common.Hash, accepted func(*types.Block)) (*BlockChain, error) { - chain, err := createBlockChain(db, cacheConfig, gspec, lastAcceptedHash) +func createAndInsertChain(db ethdb.Database, blockDb ethblockdb.Database, cacheConfig *CacheConfig, gspec *Genesis, blocks types.Blocks, lastAcceptedHash common.Hash, accepted func(*types.Block)) (*BlockChain, error) { + chain, err := createBlockChain(db, blockDb, cacheConfig, gspec, lastAcceptedHash) if err != nil { return nil, err } diff --git a/eth/backend.go b/eth/backend.go index ce68f74243..75033bf382 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -52,6 +52,7 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/customrawdb" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/rpc" "github.com/ava-labs/libevm/accounts" "github.com/ava-labs/libevm/common" @@ -91,6 +92,7 @@ type Ethereum struct { // DB interfaces chainDb ethdb.Database // Block chain database + blockDb ethblockdb.Database eventMux *event.TypeMux engine consensus.Engine @@ -130,6 +132,7 @@ func New( config *Config, gossiper PushGossiper, chainDb ethdb.Database, + blockDb ethblockdb.Database, settings Settings, lastAcceptedHash common.Hash, engine consensus.Engine, @@ -163,7 +166,7 @@ func New( // Since RecoverPruning will only continue a pruning run that already began, we do not need to ensure that // reprocessState has already been called and completed successfully. To ensure this, we must maintain // that Prune is only run after reprocessState has finished successfully. - if err := pruner.RecoverPruning(config.OfflinePruningDataDirectory, chainDb); err != nil { + if err := pruner.RecoverPruning(config.OfflinePruningDataDirectory, chainDb, blockDb); err != nil { log.Error("Failed to recover state", "error", err) } } @@ -176,6 +179,7 @@ func New( config: config, gossiper: gossiper, chainDb: chainDb, + blockDb: blockDb, eventMux: new(event.TypeMux), accountManager: stack.AccountManager(), engine: engine, @@ -183,7 +187,7 @@ func New( networkID: networkID, etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + bloomIndexer: core.NewBloomIndexer(chainDb, blockDb, params.BloomBitsBlocks, params.BloomConfirms), settings: settings, shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), } @@ -234,12 +238,12 @@ func New( if err := eth.precheckPopulateMissingTries(); err != nil { return nil, err } - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, eth.engine, vmConfig, lastAcceptedHash, config.SkipUpgradeCheck) + eth.blockchain, err = core.NewBlockChain(chainDb, blockDb, cacheConfig, config.Genesis, eth.engine, vmConfig, lastAcceptedHash, config.SkipUpgradeCheck) if err != nil { return nil, err } - if err := eth.handleOfflinePruning(cacheConfig, config.Genesis, vmConfig, lastAcceptedHash); err != nil { + if err := eth.handleOfflinePruning(cacheConfig, config.Genesis, vmConfig, lastAcceptedHash, blockDb); err != nil { return nil, err } @@ -397,6 +401,8 @@ func (s *Ethereum) Stop() error { s.chainDb.Close() log.Info("Closed chaindb") + s.blockDb.Close() + log.Info("Closed blockdb") s.eventMux.Stop() log.Info("Stopped EventMux") return nil @@ -436,7 +442,7 @@ func (s *Ethereum) precheckPopulateMissingTries() error { return nil } -func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, gspec *core.Genesis, vmConfig vm.Config, lastAcceptedHash common.Hash) error { +func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, gspec *core.Genesis, vmConfig vm.Config, lastAcceptedHash common.Hash, blockDb ethblockdb.Database) error { if s.config.OfflinePruning && !s.config.Pruning { return core.ErrRefuseToCorruptArchiver } @@ -473,7 +479,7 @@ func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, gspec *co Datadir: s.config.OfflinePruningDataDirectory, } - pruner, err := pruner.NewPruner(s.chainDb, prunerConfig) + pruner, err := pruner.NewPruner(s.chainDb, s.blockDb, prunerConfig) if err != nil { return fmt.Errorf("failed to create new pruner with data directory: %s, size: %d, due to: %w", s.config.OfflinePruningDataDirectory, s.config.OfflinePruningBloomFilterSize, err) } @@ -482,7 +488,7 @@ func (s *Ethereum) handleOfflinePruning(cacheConfig *core.CacheConfig, gspec *co } // Note: Time Marker is written inside of [Prune] before compaction begins // (considered an optional optimization) - s.blockchain, err = core.NewBlockChain(s.chainDb, cacheConfig, gspec, s.engine, vmConfig, lastAcceptedHash, s.config.SkipUpgradeCheck) + s.blockchain, err = core.NewBlockChain(s.chainDb, blockDb, cacheConfig, gspec, s.engine, vmConfig, lastAcceptedHash, s.config.SkipUpgradeCheck) if err != nil { return fmt.Errorf("failed to re-initialize blockchain after offline pruning: %w", err) } diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index b8de8d7bed..73ebbf872f 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -34,6 +34,7 @@ import ( "time" "github.com/ava-labs/coreth/core/bloombits" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/common/bitutil" "github.com/ava-labs/libevm/core/rawdb" @@ -186,7 +187,8 @@ func BenchmarkNoBloomBits(b *testing.B) { clearBloomBits(db) - _, sys := newTestFilterSystem(b, db, Config{}) + blockDb := ethblockdb.NewMock(db) + _, sys := newTestFilterSystem(b, db, blockDb, Config{}) b.Log("Running filter benchmarks...") start := time.Now() diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 9c54d7039a..8586ae897c 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -44,6 +44,7 @@ import ( "github.com/ava-labs/coreth/internal/ethapi" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/customrawdb" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/rpc" ethereum "github.com/ava-labs/libevm" "github.com/ava-labs/libevm/common" @@ -56,6 +57,7 @@ import ( type testBackend struct { db ethdb.Database + blockDb ethblockdb.Database sections uint64 txFeed event.Feed acceptedTxFeed event.Feed @@ -88,7 +90,12 @@ func (b *testBackend) GetMaxBlocksPerRequest() int64 { } func (b *testBackend) LastAcceptedBlock() *types.Block { - return rawdb.ReadHeadBlock(b.db) + hash := rawdb.ReadHeadBlockHash(b.db) + number := rawdb.ReadHeaderNumber(b.db, hash) + if number == nil { + return nil + } + return b.blockDb.ReadBlock(*number) } func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { @@ -121,7 +128,7 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe num = uint64(blockNr) hash = rawdb.ReadCanonicalHash(b.db, num) } - return rawdb.ReadHeader(b.db, hash, num), nil + return b.blockDb.ReadHeader(num), nil } func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { @@ -129,20 +136,17 @@ func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*type if number == nil { return nil, nil } - return rawdb.ReadHeader(b.db, hash, *number), nil + return b.blockDb.ReadHeader(*number), nil } func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { - if body := rawdb.ReadBody(b.db, hash, uint64(number)); body != nil { - return body, nil - } - return nil, errors.New("block body not found") + return b.blockDb.ReadBody(uint64(number)), nil } func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil { - if header := rawdb.ReadHeader(b.db, hash, *number); header != nil { - return rawdb.ReadReceipts(b.db, hash, *number, header.Time, params.TestChainConfig), nil + if header := b.blockDb.ReadHeader(*number); header != nil { + return b.blockDb.ReadReceipts(hash, *number, header.Time, params.TestChainConfig), nil } } return nil, nil @@ -216,8 +220,8 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc }() } -func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { - backend := &testBackend{db: db} +func newTestFilterSystem(t testing.TB, db ethdb.Database, blockDb ethblockdb.Database, cfg Config) (*testBackend, *FilterSystem) { + backend := &testBackend{db: db, blockDb: blockDb} sys := NewFilterSystem(backend, cfg) return backend, sys } @@ -233,14 +237,15 @@ func TestBlockSubscription(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend, sys = newTestFilterSystem(t, db, Config{}) + blockDb = ethblockdb.NewMock(db) + backend, sys = newTestFilterSystem(t, db, blockDb, Config{}) api = NewFilterAPI(sys) genesis = &core.Genesis{ Config: params.TestChainConfig, BaseFee: big.NewInt(1), } - _, chain, _, _ = core.GenerateChainWithGenesis(genesis, dummy.NewFaker(), 10, 10, func(i int, b *core.BlockGen) {}) - chainEvents = []core.ChainEvent{} + _, _, chain, _, _ = core.GenerateChainWithGenesis(genesis, dummy.NewFaker(), 10, 10, func(i int, b *core.BlockGen) {}) + chainEvents = []core.ChainEvent{} ) for _, blk := range chain { @@ -288,7 +293,8 @@ func TestPendingTxFilter(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend, sys = newTestFilterSystem(t, db, Config{}) + blockDb = ethblockdb.NewMock(db) + backend, sys = newTestFilterSystem(t, db, blockDb, Config{}) api = NewFilterAPI(sys) transactions = []*types.Transaction{ @@ -344,7 +350,8 @@ func TestPendingTxFilterFullTx(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend, sys = newTestFilterSystem(t, db, Config{}) + blockDb = ethblockdb.NewMock(db) + backend, sys = newTestFilterSystem(t, db, blockDb, Config{}) api = NewFilterAPI(sys) transactions = []*types.Transaction{ @@ -399,9 +406,10 @@ func TestPendingTxFilterFullTx(t *testing.T) { // If not it must return an error. func TestLogFilterCreation(t *testing.T) { var ( - db = rawdb.NewMemoryDatabase() - _, sys = newTestFilterSystem(t, db, Config{}) - api = NewFilterAPI(sys) + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + _, sys = newTestFilterSystem(t, db, blockDb, Config{}) + api = NewFilterAPI(sys) testCases = []struct { crit FilterCriteria @@ -448,9 +456,10 @@ func TestInvalidLogFilterCreation(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - _, sys = newTestFilterSystem(t, db, Config{}) - api = NewFilterAPI(sys) + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + _, sys = newTestFilterSystem(t, db, blockDb, Config{}) + api = NewFilterAPI(sys) ) // different situations where log filter creation should fail. @@ -476,7 +485,8 @@ func TestInvalidGetLogsRequest(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - _, sys = newTestFilterSystem(t, db, Config{}) + blockDb = ethblockdb.NewMock(db) + _, sys = newTestFilterSystem(t, db, blockDb, Config{}) api = NewFilterAPI(sys) blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") ) @@ -502,9 +512,10 @@ func TestInvalidGetRangeLogsRequest(t *testing.T) { t.Parallel() var ( - db = rawdb.NewMemoryDatabase() - _, sys = newTestFilterSystem(t, db, Config{}) - api = NewFilterAPI(sys) + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + _, sys = newTestFilterSystem(t, db, blockDb, Config{}) + api = NewFilterAPI(sys) ) if _, err := api.GetLogs(context.Background(), FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(1)}); err != errInvalidBlockRange { @@ -518,7 +529,8 @@ func TestLogFilter(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend, sys = newTestFilterSystem(t, db, Config{}) + blockDb = ethblockdb.NewMock(db) + backend, sys = newTestFilterSystem(t, db, blockDb, Config{}) api = NewFilterAPI(sys) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") @@ -632,7 +644,8 @@ func TestPendingLogsSubscription(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend, sys = newTestFilterSystem(t, db, Config{}) + blockDb = ethblockdb.NewMock(db) + backend, sys = newTestFilterSystem(t, db, blockDb, Config{}) api = NewFilterAPI(sys) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") @@ -816,7 +829,8 @@ func TestPendingTxFilterDeadlock(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - backend, sys = newTestFilterSystem(t, db, Config{Timeout: timeout}) + blockDb = ethblockdb.NewMock(db) + backend, sys = newTestFilterSystem(t, db, blockDb, Config{Timeout: timeout}) api = NewFilterAPI(sys) done = make(chan struct{}) ) @@ -886,7 +900,7 @@ func TestGetLogsRegression(t *testing.T) { genesis = &core.Genesis{ Config: params.TestChainConfig, } - _, _, _, _ = core.GenerateChainWithGenesis(genesis, dummy.NewFaker(), 10, 10, func(i int, gen *core.BlockGen) {}) + _, _, _, _, _ = core.GenerateChainWithGenesis(genesis, dummy.NewFaker(), 10, 10, func(i int, gen *core.BlockGen) {}) ) test := FilterCriteria{BlockHash: &common.Hash{}, FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64())} diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index b0fbd5a86f..b5c5632c5c 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -40,6 +40,7 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/customrawdb" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/rpc" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -62,7 +63,8 @@ func makeReceipt(addr common.Address) *types.Receipt { func BenchmarkFilters(b *testing.B) { var ( db, _ = rawdb.NewLevelDBDatabase(b.TempDir(), 0, 0, "", false) - _, sys = newTestFilterSystem(b, db, Config{}) + blockDb = ethblockdb.NewMock(db) + _, sys = newTestFilterSystem(b, db, blockDb, Config{}) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = common.BytesToAddress([]byte("jeff")) @@ -76,7 +78,7 @@ func BenchmarkFilters(b *testing.B) { } ) defer db.Close() - _, chain, receipts, err := core.GenerateChainWithGenesis(gspec, dummy.NewFaker(), 100010, 10, func(i int, gen *core.BlockGen) { + _, _, chain, receipts, err := core.GenerateChainWithGenesis(gspec, dummy.NewFaker(), 100010, 10, func(i int, gen *core.BlockGen) { switch i { case 2403: receipt := makeReceipt(addr1) @@ -100,10 +102,10 @@ func BenchmarkFilters(b *testing.B) { // The test txs are not properly signed, can't simply create a chain // and then import blocks. TODO(rjl493456442) try to get rid of the // manual database writes. - gspec.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) + gspec.MustCommit(db, blockDb, triedb.NewDatabase(db, triedb.HashDefaults)) for i, block := range chain { - rawdb.WriteBlock(db, block) + blockDb.WriteBlock(block) rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) rawdb.WriteHeadBlockHash(db, block.Hash()) rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) @@ -123,8 +125,9 @@ func BenchmarkFilters(b *testing.B) { func TestFilters(t *testing.T) { var ( - db = rawdb.NewMemoryDatabase() - _, sys = newTestFilterSystem(t, db, Config{}) + db = rawdb.NewMemoryDatabase() + blockDb = ethblockdb.NewMock(db) + _, sys = newTestFilterSystem(t, db, blockDb, Config{}) // Sender account key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key1.PublicKey) @@ -194,7 +197,7 @@ func TestFilters(t *testing.T) { // Hack: GenerateChainWithGenesis creates a new db. // Commit the genesis manually and use GenerateChain. - _, err = gspec.Commit(db, triedb.NewDatabase(db, nil)) + _, err = gspec.Commit(db, blockDb, triedb.NewDatabase(db, nil)) if err != nil { t.Fatal(err) } @@ -264,7 +267,7 @@ func TestFilters(t *testing.T) { } }) require.NoError(t, err) - bc, err := core.NewBlockChain(db, core.DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, gspec.ToBlock().Hash(), false) + bc, err := core.NewBlockChain(db, blockDb, core.DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, gspec.ToBlock().Hash(), false) if err != nil { t.Fatal(err) } diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 90de7177cd..fcf39ee6d7 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" customheader "github.com/ava-labs/coreth/plugin/evm/header" "github.com/ava-labs/coreth/plugin/evm/upgrade/acp176" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap1" @@ -106,13 +107,14 @@ func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBloc engine := dummy.NewETHFaker() // Generate testing blocks - _, blocks, _, err := core.GenerateChainWithGenesis(gspec, engine, numBlocks, ap4.TargetBlockRate-1, genBlocks) + _, _, blocks, _, err := core.GenerateChainWithGenesis(gspec, engine, numBlocks, ap4.TargetBlockRate-1, genBlocks) if err != nil { t.Fatal(err) } // Construct testing chain diskdb := rawdb.NewMemoryDatabase() - chain, err := core.NewBlockChain(diskdb, core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) + blockDb := ethblockdb.NewMock(diskdb) + chain, err := core.NewBlockChain(diskdb, blockDb, core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("Failed to create local chain, %v", err) } @@ -140,12 +142,14 @@ func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, ext }) // Generate testing blocks - _, blocks, _, err := core.GenerateChainWithGenesis(gspec, engine, numBlocks, ap4.TargetBlockRate-1, genBlocks) + _, _, blocks, _, err := core.GenerateChainWithGenesis(gspec, engine, numBlocks, ap4.TargetBlockRate-1, genBlocks) if err != nil { t.Fatal(err) } // Construct testing chain - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) + db := rawdb.NewMemoryDatabase() + blockDb := ethblockdb.NewMock(db) + chain, err := core.NewBlockChain(db, blockDb, core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("Failed to create local chain, %v", err) } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index f5ab3ac841..1e0513d1f9 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -44,6 +44,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/internal/ethapi" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/rpc" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/common/hexutil" @@ -66,6 +67,7 @@ type testBackend struct { engine consensus.Engine chaindb ethdb.Database chain *core.BlockChain + blockDb ethblockdb.Database refHook func() // Hook is invoked when the requested state is referenced relHook func() // Hook is invoked when the requested state is released @@ -74,13 +76,15 @@ type testBackend struct { // testBackend creates a new test backend. OBS: After test is done, teardown must be // invoked in order to release associated resources. func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + db := rawdb.NewMemoryDatabase() backend := &testBackend{ chainConfig: gspec.Config, engine: dummy.NewETHFaker(), - chaindb: rawdb.NewMemoryDatabase(), + chaindb: db, + blockDb: ethblockdb.NewMock(db), } // Generate blocks for testing - _, blocks, _, err := core.GenerateChainWithGenesis(gspec, backend.engine, n, 10, generator) + _, _, blocks, _, err := core.GenerateChainWithGenesis(gspec, backend.engine, n, 10, generator) if err != nil { t.Fatal(err) } @@ -93,7 +97,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i SnapshotLimit: 128, Pruning: false, // Archive mode } - chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, backend.engine, vm.Config{}, common.Hash{}, false) + chain, err := core.NewBlockChain(backend.chaindb, backend.blockDb, cacheConfig, gspec, backend.engine, vm.Config{}, common.Hash{}, false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -135,7 +139,7 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) func (b *testBackend) BadBlocks() ([]*types.Block, []*core.BadBlockReason) { return nil, nil } func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { - tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) + tx, hash, blockNumber, index := b.blockDb.ReadTransaction(txHash) return tx != nil, tx, hash, blockNumber, index, nil } diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go index b9b40c5166..4156f4d38f 100644 --- a/ethclient/simulated/backend.go +++ b/ethclient/simulated/backend.go @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/coreth/interfaces" "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/rpc" ethereum "github.com/ava-labs/libevm" "github.com/ava-labs/libevm/common" @@ -121,6 +122,7 @@ func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config, // must not be started and will be started by this method. func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) { chaindb := rawdb.NewMemoryDatabase() + blockdb := ethblockdb.NewMock(chaindb) clock := &mockable.Clock{} clock.Set(time.Unix(0, 0)) @@ -129,7 +131,7 @@ func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backe ) backend, err := eth.New( - stack, conf, &fakePushGossiper{}, chaindb, eth.Settings{}, common.Hash{}, + stack, conf, &fakePushGossiper{}, chaindb, blockdb, eth.Settings{}, common.Hash{}, engine, clock, ) if err != nil { diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 529642febc..24e71f195e 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -49,6 +49,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/internal/blocktest" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" "github.com/ava-labs/coreth/rpc" "github.com/ava-labs/coreth/utils" @@ -56,7 +57,6 @@ import ( "github.com/ava-labs/libevm/accounts/keystore" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/common/hexutil" - "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/core/vm" "github.com/ava-labs/libevm/crypto" @@ -437,10 +437,11 @@ func newTestAccountManager(t *testing.T) (*accounts.Manager, accounts.Account) { } type testBackend struct { - db ethdb.Database - chain *core.BlockChain - accman *accounts.Manager - acc accounts.Account + db ethdb.Database + blockDb ethblockdb.Database + chain *core.BlockChain + accman *accounts.Manager + acc accounts.Account } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { @@ -455,8 +456,8 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E accman, acc := newTestAccountManager(t) gspec.Alloc[acc.Address] = types.Account{Balance: big.NewInt(params.Ether)} // Generate blocks for testing - db, blocks, _, _ := core.GenerateChainWithGenesis(gspec, engine, n, 10, generator) - chain, err := core.NewBlockChain(db, cacheConfig, gspec, engine, vm.Config{}, gspec.ToBlock().Hash(), false) + db, blockDb, blocks, _, _ := core.GenerateChainWithGenesis(gspec, engine, n, 10, generator) + chain, err := core.NewBlockChain(db, blockDb, cacheConfig, gspec, engine, vm.Config{}, gspec.ToBlock().Hash(), false) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -470,7 +471,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E } chain.DrainAcceptorQueue() - backend := &testBackend{db: db, chain: chain, accman: accman, acc: acc} + backend := &testBackend{db: db, blockDb: blockDb, chain: chain, accman: accman, acc: acc} return backend } @@ -557,7 +558,7 @@ func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.R if header == nil || err != nil { return nil, err } - receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), header.Time, b.chain.Config()) + receipts := b.blockDb.ReadReceipts(hash, header.Number.Uint64(), header.Time, b.chain.Config()) return receipts, nil } func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM { @@ -584,7 +585,7 @@ func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) er panic("implement me") } func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { - tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) + tx, blockHash, blockNumber, index := b.blockDb.ReadTransaction(txHash) return true, tx, blockHash, blockNumber, index, nil } func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") } diff --git a/plugin/evm/ethblockdb/block_test.go b/plugin/evm/ethblockdb/block_test.go new file mode 100644 index 0000000000..47046d6585 --- /dev/null +++ b/plugin/evm/ethblockdb/block_test.go @@ -0,0 +1,135 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package ethblockdb + +import ( + "math" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/x/blockdb" + "github.com/ava-labs/coreth/internal/blocktest" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/types" +) + +// createTestBlock creates a test block with transactions +func createTestBlock() *types.Block { + // Create test header + header := &types.Header{ + ParentHash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), + Difficulty: big.NewInt(0x020000), + Number: big.NewInt(1), + GasLimit: 0x2fefd8, + Time: 0x55ba467c, + Extra: []byte("test block extra data"), + } + + // Create test transactions + tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) + tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) + tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) + txs := []*types.Transaction{tx1, tx2, tx3} + + // Create block + block := types.NewBlock(header, txs, nil, nil, blocktest.NewHasher()) + return block +} + +func newDatabase(t *testing.T) Database { + dir := t.TempDir() + db, err := blockdb.New(blockdb.DefaultConfig().WithDir(dir), logging.NoLog{}) + require.NoError(t, err) + chainDb := rawdb.NewMemoryDatabase() + return New(db, chainDb) +} + +func checkHeader(t *testing.T, originalBlock *types.Block, readHeader *types.Header) { + require.Equal(t, originalBlock.Header().ParentHash, readHeader.ParentHash) + require.Equal(t, originalBlock.Header().Number, readHeader.Number) + require.Equal(t, originalBlock.Header().GasLimit, readHeader.GasLimit) + require.Equal(t, originalBlock.Header().Time, readHeader.Time) + require.Equal(t, originalBlock.Header().Extra, readHeader.Extra) + require.Equal(t, originalBlock.Header().Difficulty, readHeader.Difficulty) + require.Equal(t, originalBlock.Header().Coinbase, readHeader.Coinbase) +} + +func checkBody(t *testing.T, originalBlock *types.Block, readBody *types.Body) { + require.Equal(t, len(originalBlock.Transactions()), len(readBody.Transactions)) + require.Equal(t, len(originalBlock.Uncles()), len(readBody.Uncles)) + + // Verify transaction hashes match + for i, tx := range originalBlock.Transactions() { + require.Equal(t, tx.Hash(), readBody.Transactions[i].Hash()) + } +} + +func TestWriteBlock(t *testing.T) { + db := newDatabase(t) + block := createTestBlock() + db.WriteBlock(block) + readBlock := db.ReadBlock(block.NumberU64()) + require.NotNil(t, readBlock) +} + +func TestReadHeader(t *testing.T) { + db := newDatabase(t) + originalBlock := createTestBlock() + + // Write block + db.WriteBlock(originalBlock) + + // read and check header + readHeader := db.ReadHeader(1) + require.NotNil(t, readHeader) + checkHeader(t, originalBlock, readHeader) +} + +func TestReadBody(t *testing.T) { + db := newDatabase(t) + originalBlock := createTestBlock() + + // Write block + db.WriteBlock(originalBlock) + + // read and check body + readBody := db.ReadBody(1) + require.NotNil(t, readBody) + checkBody(t, originalBlock, readBody) +} + +func TestReadBlock(t *testing.T) { + db := newDatabase(t) + originalBlock := createTestBlock() + + // Write block + db.WriteBlock(originalBlock) + + // read and check block + readBlock := db.ReadBlock(1) + require.NotNil(t, readBlock) + checkBody(t, originalBlock, readBlock.Body()) + checkHeader(t, originalBlock, readBlock.Header()) +} + +func TestHasBlock(t *testing.T) { + db := newDatabase(t) + originalBlock := createTestBlock() + + // Write block + db.WriteBlock(originalBlock) + + hasBlock := db.HasBlock(1) + require.True(t, hasBlock) + + hasBlock = db.HasBlock(2) + require.False(t, hasBlock) + + hasBlock = db.HasBlock(math.MaxUint64) + require.False(t, hasBlock) +} diff --git a/plugin/evm/ethblockdb/db.go b/plugin/evm/ethblockdb/db.go new file mode 100644 index 0000000000..2dc2210a03 --- /dev/null +++ b/plugin/evm/ethblockdb/db.go @@ -0,0 +1,361 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package ethblockdb + +import ( + "math/big" + "runtime" + "sync/atomic" + "time" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/x/blockdb" + "github.com/ava-labs/coreth/consensus/misc/eip4844" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/common/prque" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/log" + "github.com/ava-labs/libevm/rlp" +) + +type Database interface { + ReadBody(height uint64) *types.Body + ReadHeader(height uint64) *types.Header + ReadBlock(height uint64) *types.Block + HasBlock(height uint64) bool + WriteBlock(block *types.Block) + ReadReceipts(hash common.Hash, number uint64, time uint64, config *params.ChainConfig) types.Receipts + ReadTransaction(hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) + UnindexTransactions(from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, report bool) + Close() error +} + +type db struct { + database.BlockDatabase + chainDb ethdb.Database +} + +func New(database database.BlockDatabase, chainDb ethdb.Database) *db { + return &db{BlockDatabase: database, chainDb: chainDb} +} + +func Copy(d Database, chainDb ethdb.Database) Database { + db, ok := d.(*db) + if !ok { + return nil + } + return New(db.BlockDatabase, chainDb) +} + +func NewMock(chainDb ethdb.Database) *db { + return New(blockdb.NewMock(), chainDb) +} + +func (d *db) WriteBlock(block *types.Block) { + header := block.Header() + body := block.Body() + + rlpHeader, err := rlp.EncodeToBytes(header) + if err != nil { + log.Error("failed to rlp encode header on write block", "error", err) + } + headerSize := uint32(len(rlpHeader)) + + rlpBody, err := rlp.EncodeToBytes(body) + if err != nil { + log.Error("failed to rlp encode block on write block", "error", err) + } + data := append(rlpHeader, rlpBody...) + + // Store the RLP encoded block data with header size + err = d.BlockDatabase.WriteBlock(block.NumberU64(), data, headerSize) + if err != nil { + log.Error("failed to write block to block db on write block", "error", err) + } + + rawdb.WriteHeaderNumber(d.chainDb, block.Hash(), block.NumberU64()) +} + +func (d *db) ReadBody(height uint64) *types.Body { + data, err := d.BlockDatabase.ReadBody(height) + if err != nil || len(data) == 0 { + return nil + } + body := new(types.Body) + if err := rlp.DecodeBytes(data, body); err != nil { + return nil + } + return body +} + +func (d *db) ReadHeader(height uint64) *types.Header { + data, err := d.BlockDatabase.ReadHeader(height) + if err != nil || len(data) == 0 { + return nil + } + header := new(types.Header) + if err := rlp.DecodeBytes(data, header); err != nil { + return nil + } + return header +} + +func (d *db) ReadBlock(height uint64) *types.Block { + block, err := d.BlockDatabase.ReadBlock(height) + if err != nil || len(block) == 0 { + return nil + } + + _, _, bodyData, err := rlp.Split(block) + if err != nil { + return nil + } + headerSize := len(block) - len(bodyData) + headerData := block[:headerSize] + + body := new(types.Body) + if err := rlp.DecodeBytes(bodyData, body); err != nil { + return nil + } + header := new(types.Header) + if err := rlp.DecodeBytes(headerData, header); err != nil { + return nil + } + return types.NewBlockWithHeader(header).WithBody(*body).WithWithdrawals(body.Withdrawals) +} + +func (d *db) HasBlock(height uint64) bool { + ok, err := d.BlockDatabase.HasBlock(height) + if err != nil { + log.Error("failed to check if block exists in block db", "error", err) + return false + } + return ok +} + +func (d *db) ReadReceipts(hash common.Hash, number uint64, time uint64, config *params.ChainConfig) types.Receipts { + receipts := rawdb.ReadRawReceipts(d.chainDb, hash, number) + if receipts == nil { + return nil + } + block := d.ReadBlock(number) + if block == nil || block.Hash() != hash { + return nil + } + header := block.Header() + body := block.Body() + var baseFee *big.Int + if header == nil { + baseFee = big.NewInt(0) + } else { + baseFee = header.BaseFee + } + + // Compute effective blob gas price. + var blobGasPrice *big.Int + if header != nil && header.ExcessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } + if err := receipts.DeriveFields(config, hash, number, time, baseFee, blobGasPrice, body.Transactions); err != nil { + log.Error("ReadReceipts: receipts.DeriveFields failed", "hash", hash, "number", number, "error", err) + return nil + } + return receipts +} + +// ReadTransaction retrieves a specific transaction from the database, along with +// its added positional metadata. +func (d *db) ReadTransaction(hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { + blockNumber := rawdb.ReadTxLookupEntry(d.chainDb, hash) + if blockNumber == nil { + return nil, common.Hash{}, 0, 0 + } + block := d.ReadBlock(*blockNumber) + if block == nil { + return nil, common.Hash{}, 0, 0 + } + for txIndex, tx := range block.Body().Transactions { + if tx.Hash() == hash { + return tx, block.Header().Hash(), *blockNumber, uint64(txIndex) + } + } + return nil, common.Hash{}, 0, 0 +} + +func (d *db) Close() error { + return d.BlockDatabase.Close() +} + +type blockTxHashes struct { + number uint64 + hashes []common.Hash +} + +// UnindexTransactions removes txlookup indices of the specified block range. +// The from is included while to is excluded. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func (d *db) UnindexTransactions(from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, report bool) { + // short circuit for invalid range + if from >= to { + return + } + var ( + hashesCh = d.iterateTransactions(from, to, false, interrupt) + batch = d.chainDb.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + + // we expect the first number to come in to be [from]. Therefore, setting + // nextNum to from means that the queue gap-evaluation will work correctly + nextNum = from + queue = prque.New[int64, *blockTxHashes](nil) + blocks, txs = 0, 0 // for stats reporting + ) + // Otherwise spin up the concurrent iterator and unindexer + for delivery := range hashesCh { + // Push the delivery into the queue and process contiguous ranges. + queue.Push(delivery, -int64(delivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); -priority != int64(nextNum) { + break + } + // For testing + if hook != nil && !hook(nextNum) { + break + } + delivery := queue.PopItem() + nextNum = delivery.number + 1 + rawdb.DeleteTxLookupEntries(batch, delivery.hashes) + txs += len(delivery.hashes) + blocks++ + + // If enough data was accumulated in memory or we're at the last block, dump to disk + // A batch counts the size of deletion as '1', so we need to flush more + // often than that. + if blocks%1000 == 0 { + rawdb.WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to unindex, but the tail has to + // be flushed anyway. + rawdb.WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + logger := log.Debug + if report { + logger = log.Info + } + select { + case <-interrupt: + logger("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + default: + logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +// iterateTransactions iterates over all transactions in the (canon) block +// number(s) given, and yields the hashes on a channel. If there is a signal +// received from interrupt channel, the iteration will be aborted and result +// channel will be closed. +func (d *db) iterateTransactions(from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes { + // One thread sequentially reads data from db + type numberRlp struct { + number uint64 + rlp rlp.RawValue + } + if to == from { + return nil + } + threads := to - from + if cpus := runtime.NumCPU(); threads > uint64(cpus) { + threads = uint64(cpus) + } + var ( + rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel + hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh + ) + // lookup runs in one instance + lookup := func() { + n, end := from, to + if reverse { + n, end = to-1, from-1 + } + defer close(rlpCh) + for n != end { + data, err := d.BlockDatabase.ReadBody(n) + if err != nil { + log.Warn("Failed to read block body", "block", n, "error", err) + return + } + // Feed the block to the aggregator, or abort on interrupt + select { + case rlpCh <- &numberRlp{n, data}: + case <-interrupt: + return + } + if reverse { + n-- + } else { + n++ + } + } + } + // process runs in parallel + var nThreadsAlive atomic.Int32 + nThreadsAlive.Store(int32(threads)) + process := func() { + defer func() { + // Last processor closes the result channel + if nThreadsAlive.Add(-1) == 0 { + close(hashesCh) + } + }() + for data := range rlpCh { + var body types.Body + if err := rlp.DecodeBytes(data.rlp, &body); err != nil { + log.Warn("Failed to decode block body", "block", data.number, "error", err) + return + } + var hashes []common.Hash + for _, tx := range body.Transactions { + hashes = append(hashes, tx.Hash()) + } + result := &blockTxHashes{ + hashes: hashes, + number: data.number, + } + // Feed the block to the aggregator, or abort on interrupt + select { + case hashesCh <- result: + case <-interrupt: + return + } + } + } + go lookup() // start the sequential db accessor + for i := 0; i < int(threads); i++ { + go process() + } + return hashesCh +} diff --git a/plugin/evm/gossip_test.go b/plugin/evm/gossip_test.go index 8c71c559e7..2dbdf990f3 100644 --- a/plugin/evm/gossip_test.go +++ b/plugin/evm/gossip_test.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/coreth/core/txpool" "github.com/ava-labs/coreth/core/txpool/legacypool" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/utils" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" @@ -94,12 +95,13 @@ func TestGossipSubscribe(t *testing.T) { func setupPoolWithConfig(t *testing.T, config *params.ChainConfig, fundedAddress common.Address) *txpool.TxPool { diskdb := rawdb.NewMemoryDatabase() engine := dummy.NewETHFaker() + blockDb := ethblockdb.NewMock(diskdb) gspec := &core.Genesis{ Config: config, Alloc: types.GenesisAlloc{fundedAddress: {Balance: big.NewInt(1000000000000000000)}}, } - chain, err := core.NewBlockChain(diskdb, core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) + chain, err := core.NewBlockChain(diskdb, blockDb, core.DefaultCacheConfig, gspec, engine, vm.Config{}, common.Hash{}, false) require.NoError(t, err) testTxPoolConfig := legacypool.DefaultConfig legacyPool := legacypool.New(testTxPoolConfig, chain) diff --git a/plugin/evm/sync/client.go b/plugin/evm/sync/client.go index af92c3ad80..c250c3dc7a 100644 --- a/plugin/evm/sync/client.go +++ b/plugin/evm/sync/client.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/coreth/core/state/snapshot" "github.com/ava-labs/coreth/eth" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ava-labs/coreth/sync/statesync" "github.com/ava-labs/libevm/common" @@ -69,6 +70,7 @@ type ClientConfig struct { Chain *eth.Ethereum State *chain.State ChaindDB ethdb.Database + BlockDb ethblockdb.Database Acceptor BlockAcceptor VerDB *versiondb.Database MetadataDB database.Database @@ -262,7 +264,11 @@ func (client *client) syncBlocks(ctx context.Context, fromHash common.Hash, from // first, check for blocks already available on disk so we don't // request them from peers. for parentsToGet >= 0 { - blk := rawdb.ReadBlock(client.ChaindDB, nextHash, nextHeight) + blk := client.BlockDb.ReadBlock(nextHeight) + if blk != nil && blk.Hash() != nextHash { + log.Error("sync blocks: block hash mismatch", "expected", nextHash, "got", blk.Hash()) + blk = nil + } if blk != nil { // block exists nextHash = blk.ParentHash() @@ -288,7 +294,7 @@ func (client *client) syncBlocks(ctx context.Context, fromHash common.Hash, from return err } for _, block := range blocks { - rawdb.WriteBlock(batch, block) + client.BlockDb.WriteBlock(block) rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) i-- diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 82a628a077..29691585e3 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -40,6 +40,7 @@ import ( "github.com/ava-labs/coreth/plugin/evm/customrawdb" "github.com/ava-labs/coreth/plugin/evm/customtypes" "github.com/ava-labs/coreth/plugin/evm/database" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" syncervm "github.com/ava-labs/coreth/plugin/evm/sync" "github.com/ava-labs/coreth/predicate" statesyncclient "github.com/ava-labs/coreth/sync/client" @@ -355,7 +356,7 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s // and update the vm's state so the trie with accounts will // be returned by StateSyncGetLastSummary lastAccepted := server.vm.blockChain.LastAcceptedBlock() - patchedBlock := patchBlock(lastAccepted, root, server.vm.chaindb) + patchedBlock := patchBlock(lastAccepted, root, server.vm.chaindb, server.vm.blockdb) blockBytes, err := rlp.EncodeToBytes(patchedBlock) require.NoError(err) internalBlock, err := server.vm.parseBlock(context.Background(), blockBytes) @@ -507,7 +508,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { lastNumber := syncerVM.blockChain.LastAcceptedBlock().NumberU64() // check the last block is indexed - lastSyncedBlock := rawdb.ReadBlock(syncerVM.chaindb, rawdb.ReadCanonicalHash(syncerVM.chaindb, lastNumber), lastNumber) + lastSyncedBlock := syncerVM.blockdb.ReadBlock(lastNumber) for _, tx := range lastSyncedBlock.Transactions() { index := rawdb.ReadTxLookupEntry(syncerVM.chaindb, tx.Hash()) require.NotNilf(index, "Miss transaction indices, number %d hash %s", lastNumber, tx.Hash().Hex()) @@ -517,7 +518,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { if syncerVM.ethConfig.TransactionHistory != 0 { tail := lastSyncedBlock.NumberU64() - coretest.CheckTxIndices(t, &tail, tail, tail, tail, syncerVM.chaindb, true) + coretest.CheckTxIndices(t, &tail, tail, tail, tail, syncerVM.chaindb, syncerVM.blockdb, true) } blocksToBuild := 10 @@ -548,7 +549,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { if tail < lastSyncedBlock.NumberU64() { tail = lastSyncedBlock.NumberU64() } - coretest.CheckTxIndices(t, &tail, tail, block.NumberU64(), block.NumberU64(), syncerVM.chaindb, true) + coretest.CheckTxIndices(t, &tail, tail, block.NumberU64(), block.NumberU64(), syncerVM.chaindb, syncerVM.blockdb, true) } }, ) @@ -592,7 +593,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { if tail < lastSyncedBlock.NumberU64() { tail = lastSyncedBlock.NumberU64() } - coretest.CheckTxIndices(t, &tail, tail, block.NumberU64(), block.NumberU64(), syncerVM.chaindb, true) + coretest.CheckTxIndices(t, &tail, tail, block.NumberU64(), block.NumberU64(), syncerVM.chaindb, syncerVM.blockdb, true) } }, ) @@ -603,14 +604,14 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { // This breaks the digestibility of the chain since after this call // [blk] does not necessarily define a state transition from its parent // state to the new state root. -func patchBlock(blk *types.Block, root common.Hash, db ethdb.Database) *types.Block { +func patchBlock(blk *types.Block, root common.Hash, db ethdb.Database, blkDb ethblockdb.Database) *types.Block { header := blk.Header() header.Root = root receipts := rawdb.ReadRawReceipts(db, blk.Hash(), blk.NumberU64()) newBlk := customtypes.NewBlockWithExtData( header, blk.Transactions(), blk.Uncles(), receipts, trie.NewStackTrie(nil), customtypes.BlockExtData(blk), true, ) - rawdb.WriteBlock(db, newBlk) + blkDb.WriteBlock(newBlk) rawdb.WriteCanonicalHash(db, newBlk.Hash(), newBlk.NumberU64()) return newBlk } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index aa079347b5..b2fa4a6744 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/acp118" "github.com/ava-labs/coreth/network" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/extension" "github.com/ava-labs/coreth/plugin/evm/gossip" "github.com/ava-labs/coreth/plugin/evm/vmerrors" @@ -208,6 +209,9 @@ type VM struct { // [db] is the VM's current database db database.Database + // [blockdb] is the VM's current block database + blockdb ethblockdb.Database + // metadataDB is used to store one off keys. metadataDB database.Database @@ -536,6 +540,7 @@ func (vm *VM) initializeChain(lastAcceptedHash common.Hash) error { &vm.ethConfig, &EthPushGossiper{vm: vm}, vm.chaindb, + vm.blockdb, eth.Settings{MaxBlocksPerRequest: vm.config.MaxBlocksPerRequest}, lastAcceptedHash, dummy.NewDummyEngine( @@ -646,6 +651,7 @@ func (vm *VM) initializeStateSync(lastAcceptedHeight uint64) error { RequestSize: vm.config.StateSyncRequestSize, LastAcceptedHeight: lastAcceptedHeight, // TODO clean up how this is passed around ChaindDB: vm.chaindb, + BlockDb: vm.blockdb, VerDB: vm.versiondb, MetadataDB: vm.metadataDB, Acceptor: vm, @@ -712,6 +718,7 @@ func (vm *VM) SetState(_ context.Context, state snow.State) error { // onBootstrapStarted marks this VM as bootstrapping func (vm *VM) onBootstrapStarted() error { vm.bootstrapped.Set(false) + log.Info("onBootstrapStarted", "time", time.Now().UTC()) if err := vm.Client.Error(); err != nil { return err } @@ -731,6 +738,8 @@ func (vm *VM) onNormalOperationsStarted() error { return nil } vm.bootstrapped.Set(true) + log.Info("onNormalOperationsStarted", "time", time.Now().UTC()) + // Initialize goroutines related to block building // once we enter normal operation as there is no need to handle mempool gossip before this point. return vm.initBlockBuilding() @@ -1213,3 +1222,7 @@ func (vm *VM) stateSyncEnabled(lastAcceptedHeight uint64) bool { func (vm *VM) PutLastAcceptedID(ID ids.ID) error { return vm.acceptedBlockDB.Put(lastAcceptedKey, ID[:]) } + +func (vm *VM) CreateHTTP2Handler(ctx context.Context) (http.Handler, error) { + return nil, nil +} diff --git a/plugin/evm/vm_database.go b/plugin/evm/vm_database.go index 4719cee542..c8349a875e 100644 --- a/plugin/evm/vm_database.go +++ b/plugin/evm/vm_database.go @@ -4,17 +4,26 @@ package evm import ( + "path/filepath" + "strconv" "time" avalanchedatabase "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/meterblockdb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" + "github.com/ava-labs/avalanchego/x/blockdb" "github.com/ava-labs/coreth/plugin/evm/database" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/log" ) +const ( + blockDbFolder = "blockdb" +) + // initializeDBs initializes the databases used by the VM. // coreth always uses the avalanchego provided database. func (vm *VM) initializeDBs(db avalanchedatabase.Database) error { @@ -29,6 +38,21 @@ func (vm *VM) initializeDBs(db avalanchedatabase.Database) error { // that warp signatures are committed to the database atomically with // the last accepted block. vm.warpDB = prefixdb.New(warpPrefix, db) + + // Initialize block database + versionPath := strconv.FormatUint(uint64(blockdb.IndexFileVersion), 10) + blockDBPath := filepath.Join(vm.ctx.ChainDataDir, blockDbFolder, versionPath) + config := blockdb.DefaultConfig().WithDir(blockDBPath).WithSyncToDisk(false).WithCompressBlocks(true) + blockDatabase, err := blockdb.New(config, vm.ctx.Log) + if err != nil { + return err + } + meteredBlockDB, err := meterblockdb.New(vm.sdkMetrics, "blockdb", blockDatabase) + if err != nil { + return err + } + vm.blockdb = ethblockdb.New(meteredBlockDB, vm.chaindb) + return nil } diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 2a04988c47..784acdd438 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -193,6 +193,7 @@ func newVM(t *testing.T, config testVMConfig) *testVM { fork = *config.fork } ctx.NetworkUpgrades = upgradetest.GetConfig(fork) + ctx.ChainDataDir = t.TempDir() if len(config.genesisJSON) == 0 { config.genesisJSON = genesisJSON(forkToChainConfig[fork]) @@ -270,6 +271,7 @@ func setupGenesis( ) { ctx := snowtest.Context(t, snowtest.CChainID) ctx.NetworkUpgrades = upgradetest.GetConfig(fork) + ctx.ChainDataDir = t.TempDir() baseDB := memdb.New() @@ -719,6 +721,7 @@ func TestBuildEthTxBlock(t *testing.T) { restartedVM := atomicvm.WrapVM(&VM{}) newCTX := snowtest.Context(t, snowtest.CChainID) + newCTX.ChainDataDir = tvm.vm.ctx.ChainDataDir newCTX.NetworkUpgrades = upgradetest.GetConfig(fork) if err := restartedVM.Initialize( context.Background(), @@ -1692,7 +1695,7 @@ func TestReorgProtection(t *testing.T) { } if err := vm1BlkC.Accept(context.Background()); !strings.Contains(err.Error(), "expected accepted block to have parent") { - t.Fatalf("Unexpected error when setting block at finalized height: %s", err) + t.Fatalf("Unexpected error when setting block at finalized height: %d", err) } } @@ -2461,7 +2464,6 @@ func TestAcceptReorg(t *testing.T) { if err := vm1BlkA.Verify(context.Background()); err != nil { t.Fatalf("Block failed verification on VM1: %s", err) } - if err := tvm1.vm.SetPreference(context.Background(), vm1BlkA.ID()); err != nil { t.Fatal(err) } @@ -2546,7 +2548,6 @@ func TestAcceptReorg(t *testing.T) { if err := vm2BlkC.Verify(context.Background()); err != nil { t.Fatalf("BlkC failed verification on VM2: %s", err) } - if err := tvm2.vm.SetPreference(context.Background(), vm2BlkC.ID()); err != nil { t.Fatal(err) } @@ -3701,6 +3702,7 @@ func TestSkipChainConfigCheckCompatible(t *testing.T) { // use the block's timestamp instead of 0 since rewind to genesis // is hardcoded to be allowed in core/genesis.go. newCTX := snowtest.Context(t, tvm.vm.ctx.ChainID) + newCTX.ChainDataDir = tvm.vm.ctx.ChainDataDir upgradetest.SetTimesTo(&newCTX.NetworkUpgrades, upgradetest.Latest, upgrade.UnscheduledActivationTime) upgradetest.SetTimesTo(&newCTX.NetworkUpgrades, fork+1, blk.Timestamp()) upgradetest.SetTimesTo(&newCTX.NetworkUpgrades, fork, upgrade.InitiallyActiveTime) @@ -3871,7 +3873,7 @@ func TestNoBlobsAllowed(t *testing.T) { b.AddTx(tx) } // FullFaker used to skip header verification so we can generate a block with blobs - _, blocks, _, err := core.GenerateChainWithGenesis(gspec, dummy.NewFullFaker(), 1, 10, blockGen) + _, _, blocks, _, err := core.GenerateChainWithGenesis(gspec, dummy.NewFullFaker(), 1, 10, blockGen) require.NoError(err) // Create a VM with the genesis (will use header verification) diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index ab6267835c..ac376b6eac 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -45,7 +45,6 @@ import ( "github.com/ava-labs/coreth/utils" "github.com/ava-labs/coreth/warp" "github.com/ava-labs/libevm/common" - "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/crypto" "github.com/stretchr/testify/require" @@ -121,7 +120,7 @@ func TestSendWarpMessage(t *testing.T) { // Verify that the constructed block contains the expected log with an unsigned warp message in the log data ethBlock1 := blk.(*chain.BlockWrapper).Block.(extension.ExtendedBlock).GetEthBlock() require.Len(ethBlock1.Transactions(), 1) - receipts := rawdb.ReadReceipts(tvm.vm.chaindb, ethBlock1.Hash(), ethBlock1.NumberU64(), ethBlock1.Time(), tvm.vm.chainConfig) + receipts := tvm.vm.blockdb.ReadReceipts(ethBlock1.Hash(), ethBlock1.NumberU64(), ethBlock1.Time(), tvm.vm.chainConfig) require.Len(receipts, 1) require.Len(receipts[0].Logs, 1) @@ -856,6 +855,7 @@ func TestSignatureRequestsToVM(t *testing.T) { func TestClearWarpDB(t *testing.T) { ctx, db, genesisBytes, _ := setupGenesis(t, upgradetest.Latest) innerVM := &VM{} + chainDataDir := ctx.ChainDataDir vm := atomicvm.WrapVM(innerVM) require.NoError(t, vm.Initialize( context.Background(), @@ -889,6 +889,7 @@ func TestClearWarpDB(t *testing.T) { vm = atomicvm.WrapVM(innerVM) // we need new context since the previous one has registered metrics. ctx, _, _, _ = setupGenesis(t, upgradetest.Latest) + ctx.ChainDataDir = chainDataDir require.NoError(t, vm.Initialize( context.Background(), ctx, @@ -913,6 +914,7 @@ func TestClearWarpDB(t *testing.T) { vm = atomicvm.WrapVM(innerVM) config := `{"prune-warp-db-enabled": true}` ctx, _, _, _ = setupGenesis(t, upgradetest.Latest) + ctx.ChainDataDir = chainDataDir require.NoError(t, vm.Initialize( context.Background(), ctx, diff --git a/plugin/evm/wrapped_block.go b/plugin/evm/wrapped_block.go index f4cad07a45..2e6518a0de 100644 --- a/plugin/evm/wrapped_block.go +++ b/plugin/evm/wrapped_block.go @@ -24,7 +24,6 @@ import ( "github.com/ava-labs/coreth/predicate" "github.com/ava-labs/libevm/common" - "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/log" "github.com/ava-labs/libevm/rlp" @@ -128,8 +127,9 @@ func (b *wrappedBlock) handlePrecompileAccept(rules extras.Rules) error { return nil } - // Read receipts from disk - receipts := rawdb.ReadReceipts(b.vm.chaindb, b.ethBlock.Hash(), b.ethBlock.NumberU64(), b.ethBlock.Time(), b.vm.chainConfig) + // Read receipts from disk or ethdb + receipts := b.vm.blockChain.GetReceiptsByHash(b.ethBlock.Hash()) + // If there are no receipts, ReadReceipts may be nil, so we check the length and confirm the ReceiptHash // is empty to ensure that missing receipts results in an error on accept. if len(receipts) == 0 && b.ethBlock.ReceiptHash() != types.EmptyRootHash { diff --git a/sync/client/client_test.go b/sync/client/client_test.go index d4298ef168..8d4b3d6131 100644 --- a/sync/client/client_test.go +++ b/sync/client/client_test.go @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/message" clientstats "github.com/ava-labs/coreth/sync/client/stats" "github.com/ava-labs/coreth/sync/handlers" @@ -144,7 +145,8 @@ func TestGetBlocks(t *testing.T) { } memdb := rawdb.NewMemoryDatabase() tdb := triedb.NewDatabase(memdb, nil) - genesis := gspec.MustCommit(memdb, tdb) + blockDb := ethblockdb.NewMock(memdb) + genesis := gspec.MustCommit(memdb, blockDb, tdb) engine := dummy.NewETHFaker() numBlocks := 110 blocks, _, err := core.GenerateChain(params.TestChainConfig, genesis, engine, memdb, numBlocks, 0, func(i int, b *core.BlockGen) {}) diff --git a/sync/handlers/block_request_test.go b/sync/handlers/block_request_test.go index 6db3ac489d..086d014d41 100644 --- a/sync/handlers/block_request_test.go +++ b/sync/handlers/block_request_test.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/ethblockdb" "github.com/ava-labs/coreth/plugin/evm/message" "github.com/ava-labs/coreth/sync/handlers/stats" "github.com/ava-labs/coreth/sync/handlers/stats/statstest" @@ -108,7 +109,8 @@ func TestBlockRequestHandler(t *testing.T) { } memdb := rawdb.NewMemoryDatabase() tdb := triedb.NewDatabase(memdb, nil) - genesis := gspec.MustCommit(memdb, tdb) + blockDb := ethblockdb.NewMock(memdb) + genesis := gspec.MustCommit(memdb, blockDb, tdb) engine := dummy.NewETHFaker() blocks, _, err := core.GenerateChain(params.TestChainConfig, genesis, engine, memdb, 96, 0, func(i int, b *core.BlockGen) {}) if err != nil { @@ -166,7 +168,8 @@ func TestBlockRequestHandlerLargeBlocks(t *testing.T) { ) memdb := rawdb.NewMemoryDatabase() tdb := triedb.NewDatabase(memdb, nil) - genesis := gspec.MustCommit(memdb, tdb) + blockDb := ethblockdb.NewMock(memdb) + genesis := gspec.MustCommit(memdb, blockDb, tdb) engine := dummy.NewETHFaker() blocks, _, err := core.GenerateChain(gspec.Config, genesis, engine, memdb, 96, 0, func(i int, b *core.BlockGen) { var data []byte @@ -220,7 +223,8 @@ func TestBlockRequestHandlerCtxExpires(t *testing.T) { } memdb := rawdb.NewMemoryDatabase() tdb := triedb.NewDatabase(memdb, nil) - genesis := gspec.MustCommit(memdb, tdb) + blockDb := ethblockdb.NewMock(memdb) + genesis := gspec.MustCommit(memdb, blockDb, tdb) engine := dummy.NewETHFaker() blocks, _, err := core.GenerateChain(params.TestChainConfig, genesis, engine, memdb, 11, 0, func(i int, b *core.BlockGen) {}) if err != nil {