From 50899d694ca8bcc7632670f5e8ebe04f38f46f47 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 17:45:26 -0800 Subject: [PATCH 001/307] parallel state prefetcher --- core/blockchain.go | 13 ++++ core/state/snapshot/snapshot.go | 3 + core/state_prefetcher.go | 106 ++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 core/state_prefetcher.go diff --git a/core/blockchain.go b/core/blockchain.go index db3a49bdb0..a6634947d9 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -72,6 +72,8 @@ var ( snapshotAccountReadTimer = metrics.NewRegisteredCounter("chain/snapshot/account/reads", nil) snapshotStorageReadTimer = metrics.NewRegisteredCounter("chain/snapshot/storage/reads", nil) snapshotCommitTimer = metrics.NewRegisteredCounter("chain/snapshot/commits", nil) + snapshotCacheMissAccount = metrics.NewRegisteredCounter("chain/snapshot/cache/miss/account", nil) + snapshotCacheMissStorage = metrics.NewRegisteredCounter("chain/snapshot/cache/miss/storage", nil) triedbCommitTimer = metrics.NewRegisteredCounter("chain/triedb/commits", nil) @@ -1315,11 +1317,18 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { } blockStateInitTimer.Inc(time.Since(substart).Milliseconds()) + // This will attempt to execute the block txs in parallel. + // This is to avoid snapshot misses during execution. + sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) + sp.Prefetch(block, statedb, bc.vmConfig, nil) + // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) activeState = statedb // Process block using the parent state as reference point + accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() + storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() receipts, logs, usedGas, err := bc.processor.Process(block, parent, statedb, bc.vmConfig) if serr := statedb.Error(); serr != nil { @@ -1330,6 +1339,10 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { return err } ptime := time.Since(pstart) + accountMissEnd := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() + storageMissEnd := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() + snapshotCacheMissAccount.Inc(accountMissEnd - accountMissStart) + snapshotCacheMissStorage.Inc(storageMissEnd - storageMissStart) // Validate the state using the default validator vstart := time.Now() diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index abb3051762..2c2d617559 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -73,6 +73,9 @@ var ( snapshotDirtyAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/read", nil) snapshotDirtyAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/write", nil) + SnapshotCleanStorageMissMeter = snapshotCleanStorageMissMeter + SnapshotCleanAccountMissMeter = snapshotCleanAccountMissMeter + snapshotDirtyStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/hit", nil) snapshotDirtyStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/miss", nil) snapshotDirtyStorageInexMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/inex", nil) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go new file mode 100644 index 0000000000..ff6c01fcc5 --- /dev/null +++ b/core/state_prefetcher.go @@ -0,0 +1,106 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "sync/atomic" + + "github.com/ava-labs/coreth/consensus" + "github.com/ava-labs/coreth/core/state" + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/core/vm" + "github.com/ava-labs/coreth/params" + "golang.org/x/sync/errgroup" +) + +// statePrefetcher is a basic Prefetcher, which blindly executes a block on top +// of an arbitrary state with the goal of prefetching potentially useful state +// data from disk before the main block processor start executing. +type statePrefetcher struct { + config *params.ChainConfig // Chain configuration options + bc *BlockChain // Canonical block chain + engine consensus.Engine // Consensus engine used for block rewards +} + +// newStatePrefetcher initialises a new statePrefetcher. +func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *statePrefetcher { + return &statePrefetcher{ + config: config, + bc: bc, + engine: engine, + } +} + +// Prefetch processes the state changes according to the Ethereum rules by running +// the transaction messages using the statedb, but any changes are discarded. The +// only goal is to pre-cache transaction signatures and state trie nodes. +func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) { + var ( + header = block.Header() + gaspool = new(GasPool).AddGas(block.GasLimit()) + blockContext = NewEVMBlockContext(header, p.bc, nil) + evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.config, header.Number, header.Time) + ) + var eg errgroup.Group + eg.SetLimit(32) // Some limits just in case. + // Iterate over and process the individual transactions + for i, tx := range block.Transactions() { + // If block precaching was interrupted, abort + if interrupt != nil && interrupt.Load() { + return + } + eg.Go(func() error { + statedb := statedb.Copy() // Create a fresh state for each transaction + // Convert the transaction into an executable message and pre-cache its sender + msg, err := TransactionToMessage(tx, signer, header.BaseFee) + if err != nil { + // NOTE: should never happen + return nil // Also invalid block, bail out + } + statedb.SetTxContext(tx.Hash(), i) + if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { + // NOTE: We don't care that the the transaction failed, we just want to pre-cache + return nil // Ugh, something went horribly wrong, bail out + } + return nil + }) + // If we're pre-byzantium, pre-load trie nodes for the intermediate root + // if !byzantium { + // statedb.IntermediateRoot(true) + // } + } + // NOTE: For now I don't want to deal with trie nodes, just snapshot cache. + // If were post-byzantium, pre-load trie nodes for the final root hash + // if byzantium { + // statedb.IntermediateRoot(true) + // } + + // Wait for all transactions to be processed + _ = eg.Wait() +} + +// precacheTransaction attempts to apply a transaction to the given state database +// and uses the input parameters for its environment. The goal is not to execute +// the transaction successfully, rather to warm up touched data slots. +func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { + // Update the evm with the new transaction context. + evm.Reset(NewEVMTxContext(msg), statedb) + // Add addresses to access list if applicable + _, err := ApplyMessage(evm, msg, gaspool) + return err +} From 7ff1e8251e3831141b58266e8c23158be65c39b1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 19:07:41 -0800 Subject: [PATCH 002/307] fix prefetching --- core/blockchain.go | 2 +- core/state_prefetcher.go | 27 ++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index a6634947d9..9c5980e9fe 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1320,7 +1320,7 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { // This will attempt to execute the block txs in parallel. // This is to avoid snapshot misses during execution. sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) - sp.Prefetch(block, statedb, bc.vmConfig, nil) + sp.Prefetch(block, parent.Root, bc.vmConfig, nil) // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index ff6c01fcc5..f9f9404669 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -24,6 +24,8 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "golang.org/x/sync/errgroup" ) @@ -48,12 +50,21 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse // Prefetch processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb, but any changes are discarded. The // only goal is to pre-cache transaction signatures and state trie nodes. -func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) { +func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, cfg vm.Config, interrupt *atomic.Bool) { + if p.bc.snaps == nil { + log.Warn("Skipping prefetching transactions without snapshot cache") + return + } + snap := p.bc.snaps.Snapshot(parentRoot) + if snap == nil { + log.Warn("Skipping prefetching transactions without snapshot cache") + return + } + var ( header = block.Header() gaspool = new(GasPool).AddGas(block.GasLimit()) blockContext = NewEVMBlockContext(header, p.bc, nil) - evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) ) var eg errgroup.Group @@ -65,11 +76,15 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return } eg.Go(func() error { - statedb := statedb.Copy() // Create a fresh state for each transaction + statedb, err := state.New(parentRoot, p.bc.stateCache, p.bc.snaps) + if err != nil { + return err + } + + evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { - // NOTE: should never happen return nil // Also invalid block, bail out } statedb.SetTxContext(tx.Hash(), i) @@ -91,7 +106,9 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // } // Wait for all transactions to be processed - _ = eg.Wait() + if err := eg.Wait(); err != nil { + log.Crit("Unexpected failure in pre-caching transactions", "err", err) + } } // precacheTransaction attempts to apply a transaction to the given state database From fd122d7a7e2491840bc4b2621dbc58841df60e96 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 19:08:54 -0800 Subject: [PATCH 003/307] reprocessing stuff --- core/blockchain.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index 9c5980e9fe..a4f40225fa 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -402,6 +402,10 @@ func NewBlockChain( return nil, err } + if err := bc.reprocessFromGenesis(); err != nil { + return nil, err + } + // After loading the last state (and reprocessing if necessary), we are // guaranteed that [acceptorTip] is equal to [lastAccepted]. // @@ -1687,6 +1691,11 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) return common.Hash{}, fmt.Errorf("could not fetch state for (%s: %d): %v", parent.Hash().Hex(), parent.NumberU64(), err) } + // This will attempt to execute the block txs in parallel. + // This is to avoid snapshot misses during execution. + sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) + sp.Prefetch(current, parent.Root(), bc.vmConfig, nil) + // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) defer func() { @@ -1694,10 +1703,16 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) }() // Process previously stored block + accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() + storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() receipts, _, usedGas, err := bc.processor.Process(current, parent.Header(), statedb, vm.Config{}) if err != nil { return common.Hash{}, fmt.Errorf("failed to re-process block (%s: %d): %v", current.Hash().Hex(), current.NumberU64(), err) } + accountMissEnd := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() + storageMissEnd := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() + snapshotCacheMissAccount.Inc(accountMissEnd - accountMissStart) + snapshotCacheMissStorage.Inc(storageMissEnd - storageMissStart) // Validate the state using the default validator if err := bc.validator.ValidateState(current, statedb, receipts, usedGas); err != nil { @@ -1714,6 +1729,63 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash()) } +// reprocessFrom destroys the current snapshot (overrides it with genesis state) and +// reprocesses the chain from the genesis block up to the current head block. +func (bc *BlockChain) reprocessFromGenesis() error { + target := bc.CurrentBlock().Number + log.Warn("Reprocessing chain from genesis", "target", target) + + if err := bc.loadGenesisState(); err != nil { + return err + } + bc.initSnapshot(bc.hc.genesisHeader) + log.Warn("Snapshot initialized with genesis state") + + parent := bc.genesisBlock + + var ( + start = time.Now() + logged time.Time + previousRoot common.Hash + triedb = bc.triedb + totalFlatTime time.Duration + ) + for i := uint64(1); i <= target.Uint64(); i++ { + current := bc.GetBlockByNumber(i) + + root, err := bc.reprocessBlock(parent, current) + if err != nil { + return err + } + + // Flatten snapshot if initialized, holding a reference to the state root until the next block + // is processed. + fstart := time.Now() + if err := bc.flattenSnapshot(func() error { + if previousRoot != (common.Hash{}) && previousRoot != root { + triedb.Dereference(previousRoot) + } + previousRoot = root + return nil + }, current.Hash()); err != nil { + return err + } + flatTime := time.Since(fstart) + totalFlatTime += flatTime + + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second { + log.Info("Reprocessing chain", "block", i, + "elapsed", common.PrettyDuration(time.Since(start).Truncate(time.Second)), + "flat", common.PrettyDuration(totalFlatTime.Truncate(time.Millisecond)), + ) + logged = time.Now() + } + } + + return nil +} + // initSnapshot instantiates a Snapshot instance and adds it to [bc] func (bc *BlockChain) initSnapshot(b *types.Header) { if bc.cacheConfig.SnapshotLimit <= 0 || bc.snaps != nil { From 089d677b318e578ace64a324d071080f75bb572f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 19:17:02 -0800 Subject: [PATCH 004/307] use accept trie --- core/blockchain.go | 10 ++-------- core/state_manager.go | 3 +++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index a4f40225fa..6d333e7ea5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1746,14 +1746,12 @@ func (bc *BlockChain) reprocessFromGenesis() error { var ( start = time.Now() logged time.Time - previousRoot common.Hash - triedb = bc.triedb totalFlatTime time.Duration ) for i := uint64(1); i <= target.Uint64(); i++ { current := bc.GetBlockByNumber(i) - root, err := bc.reprocessBlock(parent, current) + _, err := bc.reprocessBlock(parent, current) if err != nil { return err } @@ -1762,11 +1760,7 @@ func (bc *BlockChain) reprocessFromGenesis() error { // is processed. fstart := time.Now() if err := bc.flattenSnapshot(func() error { - if previousRoot != (common.Hash{}) && previousRoot != root { - triedb.Dereference(previousRoot) - } - previousRoot = root - return nil + return bc.stateManager.AcceptTrie(current) }, current.Hash()); err != nil { return err } diff --git a/core/state_manager.go b/core/state_manager.go index 8fc7de11c3..52bc7598c6 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" ) func init() { @@ -130,6 +131,7 @@ func (cm *cappedMemoryTrieWriter) InsertTrie(block *types.Block) error { if nodes <= cm.memoryCap && imgs <= cm.imageCap { return nil } + log.Warn("Trie memory cap exceeded, capping trie", "block", block.Hash().Hex(), "nodes", nodes, "images", imgs) if err := cm.TrieDB.Cap(cm.memoryCap - ethdb.IdealBatchSize); err != nil { return fmt.Errorf("failed to cap trie for block %s: %w", block.Hash().Hex(), err) } @@ -150,6 +152,7 @@ func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { } // Commit this root if we have reached the [commitInterval]. + log.Info("Committing trie", "block", block.Hash, "root", root.Hex(), "number", block.NumberU64()) modCommitInterval := block.NumberU64() % cm.commitInterval if modCommitInterval == 0 { if err := cm.TrieDB.Commit(root, true); err != nil { From 5220cfb39e76322726670e7aa31a14d7f43f4e92 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 19:56:21 -0800 Subject: [PATCH 005/307] patch --- core/blockchain.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 6d333e7ea5..95735a7285 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1732,7 +1732,7 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) // reprocessFrom destroys the current snapshot (overrides it with genesis state) and // reprocesses the chain from the genesis block up to the current head block. func (bc *BlockChain) reprocessFromGenesis() error { - target := bc.CurrentBlock().Number + target := uint64(30_000_000) log.Warn("Reprocessing chain from genesis", "target", target) if err := bc.loadGenesisState(); err != nil { @@ -1748,7 +1748,7 @@ func (bc *BlockChain) reprocessFromGenesis() error { logged time.Time totalFlatTime time.Duration ) - for i := uint64(1); i <= target.Uint64(); i++ { + for i := uint64(1); i <= target; i++ { current := bc.GetBlockByNumber(i) _, err := bc.reprocessBlock(parent, current) @@ -1775,6 +1775,8 @@ func (bc *BlockChain) reprocessFromGenesis() error { ) logged = time.Now() } + + parent = current } return nil From 9cdaa6ee238bc35d8e7412f3bbdd022f2840a7df Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 19:56:41 -0800 Subject: [PATCH 006/307] patch --- core/blockchain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 95735a7285..776edf6b25 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -397,12 +397,12 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) - // Re-generate current block state if it is missing - if err := bc.loadLastState(lastAcceptedHash); err != nil { + if err := bc.reprocessFromGenesis(); err != nil { return nil, err } - if err := bc.reprocessFromGenesis(); err != nil { + // Re-generate current block state if it is missing + if err := bc.loadLastState(lastAcceptedHash); err != nil { return nil, err } From 46ef6086e0e2789a39a6ba6b6fd68021c6a65ad0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 19:59:54 -0800 Subject: [PATCH 007/307] fix log --- core/state_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_manager.go b/core/state_manager.go index 52bc7598c6..b5713b31c0 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -152,9 +152,9 @@ func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { } // Commit this root if we have reached the [commitInterval]. - log.Info("Committing trie", "block", block.Hash, "root", root.Hex(), "number", block.NumberU64()) modCommitInterval := block.NumberU64() % cm.commitInterval if modCommitInterval == 0 { + log.Info("Committing trie", "block", block.Hash, "root", root.Hex(), "number", block.NumberU64()) if err := cm.TrieDB.Commit(root, true); err != nil { return fmt.Errorf("failed to commit trie for block %s: %w", block.Hash().Hex(), err) } From 26125ddb61fd56869f1fe22417533afa1506143a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 20:11:15 -0800 Subject: [PATCH 008/307] try --- core/blockchain.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 776edf6b25..7067a7ce54 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1768,10 +1768,16 @@ func (bc *BlockChain) reprocessFromGenesis() error { totalFlatTime += flatTime // Print progress logs if long enough time elapsed + accountMiss := snapshotCacheMissAccount.Snapshot().Count() + storageMiss := snapshotCacheMissStorage.Snapshot().Count() if time.Since(logged) > 8*time.Second { - log.Info("Reprocessing chain", "block", i, + log.Info( + "Reprocessing chain", + "block", i, "elapsed", common.PrettyDuration(time.Since(start).Truncate(time.Second)), "flat", common.PrettyDuration(totalFlatTime.Truncate(time.Millisecond)), + "accountMiss", accountMiss, + "storageMiss", storageMiss, ) logged = time.Now() } From a5225c102cfae6c95236d16eac75d27d4cf0e0db Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 21:04:24 -0800 Subject: [PATCH 009/307] more stats --- core/blockchain.go | 44 +++++++++++++++++++++++++++++++++++++++---- core/state_manager.go | 1 - 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 7067a7ce54..8f36c90c1b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1667,9 +1667,21 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e log.Debug(reason.String()) } +type extraStats struct { + spTime time.Duration + pTime time.Duration + vTime time.Duration + cTime time.Duration +} + // reprocessBlock reprocesses a previously accepted block. This is often used // to regenerate previously pruned state tries. -func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) (common.Hash, error) { +func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, extras ...*extraStats) (common.Hash, error) { + var stats *extraStats + if len(extras) > 0 { + stats = extras[0] + } + // Retrieve the parent block and its state to execute block var ( statedb *state.StateDB @@ -1693,8 +1705,10 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) // This will attempt to execute the block txs in parallel. // This is to avoid snapshot misses during execution. + spStart := time.Now() sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) sp.Prefetch(current, parent.Root(), bc.vmConfig, nil) + spTime := time.Since(spStart) // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) @@ -1705,28 +1719,45 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) // Process previously stored block accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() + pstart := time.Now() receipts, _, usedGas, err := bc.processor.Process(current, parent.Header(), statedb, vm.Config{}) if err != nil { return common.Hash{}, fmt.Errorf("failed to re-process block (%s: %d): %v", current.Hash().Hex(), current.NumberU64(), err) } + ptime := time.Since(pstart) accountMissEnd := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissEnd := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() snapshotCacheMissAccount.Inc(accountMissEnd - accountMissStart) snapshotCacheMissStorage.Inc(storageMissEnd - storageMissStart) // Validate the state using the default validator + vstart := time.Now() if err := bc.validator.ValidateState(current, statedb, receipts, usedGas); err != nil { return common.Hash{}, fmt.Errorf("failed to validate state while re-processing block (%s: %d): %v", current.Hash().Hex(), current.NumberU64(), err) } + vtime := time.Since(vstart) log.Debug("Processed block", "block", current.Hash(), "number", current.NumberU64()) // Commit all cached state changes into underlying memory database. // If snapshots are enabled, call CommitWithSnaps to explicitly create a snapshot // diff layer for the block. + var root common.Hash + cstart := time.Now() if bc.snaps == nil { - return statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number())) + root, err = statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number())) + } else { + root, err = statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash()) } - return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash()) + ctime := time.Since(cstart) + + if stats != nil { + stats.spTime += spTime + stats.pTime += ptime + stats.vTime += vtime + stats.cTime += ctime + } + + return root, err } // reprocessFrom destroys the current snapshot (overrides it with genesis state) and @@ -1743,6 +1774,7 @@ func (bc *BlockChain) reprocessFromGenesis() error { parent := bc.genesisBlock + stats := &extraStats{} var ( start = time.Now() logged time.Time @@ -1751,7 +1783,7 @@ func (bc *BlockChain) reprocessFromGenesis() error { for i := uint64(1); i <= target; i++ { current := bc.GetBlockByNumber(i) - _, err := bc.reprocessBlock(parent, current) + _, err := bc.reprocessBlock(parent, current, stats) if err != nil { return err } @@ -1776,6 +1808,10 @@ func (bc *BlockChain) reprocessFromGenesis() error { "block", i, "elapsed", common.PrettyDuration(time.Since(start).Truncate(time.Second)), "flat", common.PrettyDuration(totalFlatTime.Truncate(time.Millisecond)), + "spTime", stats.spTime.Truncate(time.Millisecond), + "pTime", stats.pTime.Truncate(time.Millisecond), + "vTime", stats.vTime.Truncate(time.Millisecond), + "cTime", stats.cTime.Truncate(time.Millisecond), "accountMiss", accountMiss, "storageMiss", storageMiss, ) diff --git a/core/state_manager.go b/core/state_manager.go index b5713b31c0..083276a5e3 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -154,7 +154,6 @@ func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { // Commit this root if we have reached the [commitInterval]. modCommitInterval := block.NumberU64() % cm.commitInterval if modCommitInterval == 0 { - log.Info("Committing trie", "block", block.Hash, "root", root.Hex(), "number", block.NumberU64()) if err := cm.TrieDB.Commit(root, true); err != nil { return fmt.Errorf("failed to commit trie for block %s: %w", block.Hash().Hex(), err) } From bdcbba9a64535c7da77b25da5736c633e67bb2cf Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 21:08:41 -0800 Subject: [PATCH 010/307] quieter log --- core/state_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_manager.go b/core/state_manager.go index 083276a5e3..2057672e98 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -154,7 +154,7 @@ func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { // Commit this root if we have reached the [commitInterval]. modCommitInterval := block.NumberU64() % cm.commitInterval if modCommitInterval == 0 { - if err := cm.TrieDB.Commit(root, true); err != nil { + if err := cm.TrieDB.Commit(root, false); err != nil { return fmt.Errorf("failed to commit trie for block %s: %w", block.Hash().Hex(), err) } return nil From d019ac537c293f67e65047384a2c50fa9f528f90 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 21:24:49 -0800 Subject: [PATCH 011/307] try again --- plugin/evm/vm.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 50606b0af7..8cc8956265 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -626,6 +626,9 @@ func (vm *VM) Initialize( if err != nil { return err } + + go vm.ctx.Log.RecoverAndPanic(vm.startContinuousProfiler) + if err := vm.initializeChain(lastAcceptedHash); err != nil { return err } @@ -655,8 +658,6 @@ func (vm *VM) Initialize( } vm.atomicTrie = vm.atomicBackend.AtomicTrie() - go vm.ctx.Log.RecoverAndPanic(vm.startContinuousProfiler) - // so [vm.baseCodec] is a dummy codec use to fulfill the secp256k1fx VM // interface. The fx will register all of its types, which can be safely // ignored by the VM's codec. From 117980746049a41494b9f4876fd0e4b32764a475 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 9 Dec 2024 21:54:45 -0800 Subject: [PATCH 012/307] add cacher recovery --- core/blockchain.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index 8f36c90c1b..2bd3d859f0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1682,6 +1682,8 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, stats = extras[0] } + bc.senderCacher.Recover(types.MakeSigner(bc.chainConfig, current.Number(), current.Time()), current.Transactions()) + // Retrieve the parent block and its state to execute block var ( statedb *state.StateDB From d8522ab1a9f63132fc4932b8ab0f4b9a787f916d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 10:37:45 -0800 Subject: [PATCH 013/307] prefetch serially --- core/state_prefetcher.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index f9f9404669..b730d6ea5a 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -67,8 +67,12 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c blockContext = NewEVMBlockContext(header, p.bc, nil) signer = types.MakeSigner(p.config, header.Number, header.Time) ) + statedb, err := state.New(parentRoot, p.bc.stateCache, p.bc.snaps) + if err != nil { + return + } var eg errgroup.Group - eg.SetLimit(32) // Some limits just in case. + eg.SetLimit(1) // Some limits just in case. // Iterate over and process the individual transactions for i, tx := range block.Transactions() { // If block precaching was interrupted, abort @@ -76,11 +80,6 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c return } eg.Go(func() error { - statedb, err := state.New(parentRoot, p.bc.stateCache, p.bc.snaps) - if err != nil { - return err - } - evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) From 25733538ce6d523daa267a79c892a0e29ef0d7b4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 11:06:56 -0800 Subject: [PATCH 014/307] make sure is working --- core/state_prefetcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index b730d6ea5a..71ea979448 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -84,12 +84,12 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { - return nil // Also invalid block, bail out + return err // Also invalid block, bail out } statedb.SetTxContext(tx.Hash(), i) if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { // NOTE: We don't care that the the transaction failed, we just want to pre-cache - return nil // Ugh, something went horribly wrong, bail out + return err // Ugh, something went horribly wrong, bail out } return nil }) From 3e99257be79068f52e1f922f27d79adde3eb4acb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 11:15:49 -0800 Subject: [PATCH 015/307] more like orig --- core/state_prefetcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 71ea979448..a1b3306ab6 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -79,8 +79,8 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c if interrupt != nil && interrupt.Load() { return } + evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) eg.Go(func() error { - evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { From c60eb3141c3bf0c6b71f0d1b249e0bf8307dcd94 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 15:49:17 -0800 Subject: [PATCH 016/307] add tape (UTs work) --- core/blockchain.go | 31 +++++++--- core/state_prefetcher.go | 127 +++++++++++++++++++++++++++++++++++---- core/state_processor.go | 11 +++- 3 files changed, 147 insertions(+), 22 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 2bd3d859f0..9730363359 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -397,9 +397,9 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) - if err := bc.reprocessFromGenesis(); err != nil { - return nil, err - } + // if err := bc.reprocessFromGenesis(); err != nil { + // return nil, err + // } // Re-generate current block state if it is missing if err := bc.loadLastState(lastAcceptedHash); err != nil { @@ -1324,7 +1324,8 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { // This will attempt to execute the block txs in parallel. // This is to avoid snapshot misses during execution. sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) - sp.Prefetch(block, parent.Root, bc.vmConfig, nil) + tape := new(tape) + sp.Prefetch(block, parent.Root, bc.vmConfig, tape) // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) @@ -1334,6 +1335,7 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() + bc.processor.(*StateProcessor).tape = *tape receipts, logs, usedGas, err := bc.processor.Process(block, parent, statedb, bc.vmConfig) if serr := statedb.Error(); serr != nil { log.Error("statedb error encountered", "err", serr, "number", block.Number(), "hash", block.Hash()) @@ -1668,10 +1670,13 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e } type extraStats struct { - spTime time.Duration - pTime time.Duration - vTime time.Duration - cTime time.Duration + spTime time.Duration + pTime time.Duration + vTime time.Duration + cTime time.Duration + tapeLen uint64 + blocks uint64 + txs uint64 } // reprocessBlock reprocesses a previously accepted block. This is often used @@ -1707,9 +1712,10 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, // This will attempt to execute the block txs in parallel. // This is to avoid snapshot misses during execution. + tape := new(tape) spStart := time.Now() sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) - sp.Prefetch(current, parent.Root(), bc.vmConfig, nil) + sp.Prefetch(current, parent.Root(), bc.vmConfig, tape) spTime := time.Since(spStart) // Enable prefetching to pull in trie node paths while processing transactions @@ -1722,6 +1728,7 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() + bc.processor.(*StateProcessor).tape = *tape receipts, _, usedGas, err := bc.processor.Process(current, parent.Header(), statedb, vm.Config{}) if err != nil { return common.Hash{}, fmt.Errorf("failed to re-process block (%s: %d): %v", current.Hash().Hex(), current.NumberU64(), err) @@ -1757,6 +1764,9 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, stats.pTime += ptime stats.vTime += vtime stats.cTime += ctime + stats.tapeLen += uint64(tape.Len()) + stats.txs += uint64(len(current.Transactions())) + stats.blocks++ } return root, err @@ -1816,6 +1826,9 @@ func (bc *BlockChain) reprocessFromGenesis() error { "cTime", stats.cTime.Truncate(time.Millisecond), "accountMiss", accountMiss, "storageMiss", storageMiss, + "tapeLen", stats.tapeLen, + "txs", stats.txs, + "blocks", stats.blocks, ) logged = time.Now() } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index a1b3306ab6..2a91810bfd 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -17,7 +17,8 @@ package core import ( - "sync/atomic" + "encoding/binary" + "math/big" "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core/state" @@ -26,6 +27,7 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" "golang.org/x/sync/errgroup" ) @@ -50,7 +52,11 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse // Prefetch processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb, but any changes are discarded. The // only goal is to pre-cache transaction signatures and state trie nodes. -func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, cfg vm.Config, interrupt *atomic.Bool) { +type tape []byte + +func (t tape) Len() int { return len(t) } + +func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, cfg vm.Config, tape *tape) { if p.bc.snaps == nil { log.Warn("Skipping prefetching transactions without snapshot cache") return @@ -74,20 +80,17 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c var eg errgroup.Group eg.SetLimit(1) // Some limits just in case. // Iterate over and process the individual transactions + vmState := &StateReadsRecorder{vmStateDB: statedb} for i, tx := range block.Transactions() { - // If block precaching was interrupted, abort - if interrupt != nil && interrupt.Load() { - return - } - evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + evm := vm.NewEVM(blockContext, vm.TxContext{}, vmState, p.config, cfg) eg.Go(func() error { // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { return err // Also invalid block, bail out } - statedb.SetTxContext(tx.Hash(), i) - if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { + vmState.SetTxContext(tx.Hash(), i) + if err := precacheTransaction(msg, p.config, gaspool, vmState, header, evm); err != nil { // NOTE: We don't care that the the transaction failed, we just want to pre-cache return err // Ugh, something went horribly wrong, bail out } @@ -106,17 +109,119 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c // Wait for all transactions to be processed if err := eg.Wait(); err != nil { - log.Crit("Unexpected failure in pre-caching transactions", "err", err) + log.Error("Unexpected failure in pre-caching transactions", "err", err) } + *tape = vmState.tape } // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { +func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb vm.StateDB, header *types.Header, evm *vm.EVM) error { // Update the evm with the new transaction context. evm.Reset(NewEVMTxContext(msg), statedb) // Add addresses to access list if applicable _, err := ApplyMessage(evm, msg, gaspool) return err } + +type vmStateDB interface { + vm.StateDB + Finalise(bool) + IntermediateRoot(bool) common.Hash + SetTxContext(common.Hash, int) + TxIndex() int + GetLogs(common.Hash, uint64, common.Hash) []*types.Log +} + +type StateReadsRecorder struct { + vmStateDB + + tape []byte +} + +type StateReadsReplayer struct { + vmStateDB + + tape []byte +} + +func (s *StateReadsRecorder) GetBalance(addr common.Address) *uint256.Int { + v := s.vmStateDB.GetBalance(addr) + bytes := v.Bytes() + s.tape = append(s.tape, byte(len(bytes))) + s.tape = append(s.tape, bytes...) + return v +} + +func (s *StateReadsReplayer) GetBalance(common.Address) *uint256.Int { + l := int(s.tape[0]) + v := new(uint256.Int) + v.SetBytes(s.tape[1 : 1+l]) + s.tape = s.tape[1+l:] + return v +} + +func (s *StateReadsRecorder) GetBalanceMultiCoin(addr common.Address, coin common.Hash) *big.Int { + v := s.vmStateDB.GetBalanceMultiCoin(addr, coin) + bytes := v.Bytes() + s.tape = append(s.tape, byte(len(bytes))) + s.tape = append(s.tape, v.Bytes()...) + return v +} + +func (s *StateReadsReplayer) GetBalanceMultiCoin(common.Address, common.Hash) *big.Int { + l := int(s.tape[0]) + v := new(big.Int) + v.SetBytes(s.tape[1 : 1+l]) + s.tape = s.tape[1+l:] + return v +} + +func (s *StateReadsRecorder) GetNonce(addr common.Address) uint64 { + v := s.vmStateDB.GetNonce(addr) + s.tape = binary.BigEndian.AppendUint64(s.tape, v) + return v +} + +func (s *StateReadsReplayer) GetNonce(common.Address) uint64 { + v := binary.BigEndian.Uint64(s.tape) + s.tape = s.tape[8:] + return v +} + +func (s *StateReadsRecorder) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + v := s.vmStateDB.GetCommittedState(addr, hash) + s.tape = append(s.tape, v.Bytes()...) + return v +} + +func (s *StateReadsReplayer) GetCommittedState(common.Address, common.Hash) common.Hash { + v := common.BytesToHash(s.tape[:32]) + s.tape = s.tape[32:] + return v +} + +func (s *StateReadsRecorder) GetCommittedStateAP1(addr common.Address, hash common.Hash) common.Hash { + v := s.vmStateDB.GetCommittedStateAP1(addr, hash) + s.tape = append(s.tape, v.Bytes()...) + return v +} + +func (s *StateReadsReplayer) GetCommittedStateAP1(common.Address, common.Hash) common.Hash { + v := common.BytesToHash(s.tape[:32]) + s.tape = s.tape[32:] + return v +} + +func (s *StateReadsRecorder) GetState(addr common.Address, hash common.Hash) common.Hash { + v := s.vmStateDB.GetState(addr, hash) + s.tape = append(s.tape, v.Bytes()...) + return v +} + +func (s *StateReadsReplayer) GetState(common.Address, common.Hash) common.Hash { + v := common.BytesToHash(s.tape[:32]) + s.tape = s.tape[32:] + return v +} diff --git a/core/state_processor.go b/core/state_processor.go index af5b888686..5a13c12611 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -51,6 +51,8 @@ type StateProcessor struct { config *params.ChainConfig // Chain configuration options bc *BlockChain // Canonical block chain engine consensus.Engine // Consensus engine used for block rewards + + tape tape } // NewStateProcessor initialises a new StateProcessor. @@ -87,6 +89,11 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return nil, nil, 0, err } + var vmState vmStateDB = statedb + if p.tape != nil { + vmState = &StateReadsRecorder{vmStateDB: statedb, tape: p.tape} + } + var ( context = NewEVMBlockContext(header, p.bc, nil) vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) @@ -102,7 +109,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } statedb.SetTxContext(tx.Hash(), i) - receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) + receipt, err := applyTransaction(msg, p.config, gp, vmState, blockNumber, blockHash, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } @@ -117,7 +124,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return receipts, allLogs, *usedGas, nil } -func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { +func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb vmStateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) From e9c4e9fa67df7497a719d7898534f1d56cc1c7c5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 15:49:54 -0800 Subject: [PATCH 017/307] reprocess again --- core/blockchain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 9730363359..1d993a4cc0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -397,9 +397,9 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) - // if err := bc.reprocessFromGenesis(); err != nil { - // return nil, err - // } + if err := bc.reprocessFromGenesis(); err != nil { + return nil, err + } // Re-generate current block state if it is missing if err := bc.loadLastState(lastAcceptedHash); err != nil { From 90066e6af485236ff878d69eb36bdde04ff815c4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 18:01:34 -0800 Subject: [PATCH 018/307] snapshot tape --- core/blockchain.go | 29 +++--- core/state_prefetcher.go | 185 +++++++++++++++++++------------------ core/state_processor.go | 7 +- plugin/evm/vm_warp_test.go | 2 + 4 files changed, 116 insertions(+), 107 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 1d993a4cc0..8e41dd8a2f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -397,9 +397,9 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) - if err := bc.reprocessFromGenesis(); err != nil { - return nil, err - } + // if err := bc.reprocessFromGenesis(); err != nil { + // return nil, err + // } // Re-generate current block state if it is missing if err := bc.loadLastState(lastAcceptedHash); err != nil { @@ -1306,7 +1306,6 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { }() // Retrieve the parent block to determine which root to build state on - substart = time.Now() parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1) // Instantiate the statedb to use for processing transactions @@ -1315,18 +1314,27 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { // entries directly from the trie (much slower). bc.flattenLock.Lock() defer bc.flattenLock.Unlock() - statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) - if err != nil { - return err - } - blockStateInitTimer.Inc(time.Since(substart).Milliseconds()) - // This will attempt to execute the block txs in parallel. // This is to avoid snapshot misses during execution. sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) tape := new(tape) sp.Prefetch(block, parent.Root, bc.vmConfig, tape) + substart = time.Now() + var statedb *state.StateDB + if bc.snaps != nil { + snap := bc.snaps.Snapshot(parent.Root) + withReplay := &snapReplay{snap, *tape} + //withReplay := snap + statedb, err = state.NewWithSnapshot(parent.Root, bc.stateCache, withReplay) + } else { + statedb, err = state.New(parent.Root, bc.stateCache, nil) + } + if err != nil { + return err + } + blockStateInitTimer.Inc(time.Since(substart).Milliseconds()) + // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) activeState = statedb @@ -1335,7 +1343,6 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() - bc.processor.(*StateProcessor).tape = *tape receipts, logs, usedGas, err := bc.processor.Process(block, parent, statedb, bc.vmConfig) if serr := statedb.Error(); serr != nil { log.Error("statedb error encountered", "err", serr, "number", block.Number(), "hash", block.Hash()) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 2a91810bfd..920fc57370 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -17,17 +17,15 @@ package core import ( - "encoding/binary" - "math/big" - "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core/state" + "github.com/ava-labs/coreth/core/state/snapshot" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/rlp" "golang.org/x/sync/errgroup" ) @@ -73,24 +71,37 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c blockContext = NewEVMBlockContext(header, p.bc, nil) signer = types.MakeSigner(p.config, header.Number, header.Time) ) - statedb, err := state.New(parentRoot, p.bc.stateCache, p.bc.snaps) + recorder := &snapRecorder{Snapshot: snap} + statedb, err := state.NewWithSnapshot(parentRoot, p.bc.stateCache, recorder) if err != nil { return } + + // Configure any upgrades that should go into effect during this block. + parent := p.bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + err = ApplyUpgrades(p.config, &parent.Time, block, statedb) + if err != nil { + log.Error("failed to configure precompiles processing block", "hash", block.Hash(), "number", block.NumberU64(), "timestamp", block.Time(), "err", err) + } + + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } var eg errgroup.Group eg.SetLimit(1) // Some limits just in case. // Iterate over and process the individual transactions - vmState := &StateReadsRecorder{vmStateDB: statedb} + results := make([]*ExecutionResult, len(block.Transactions())) for i, tx := range block.Transactions() { - evm := vm.NewEVM(blockContext, vm.TxContext{}, vmState, p.config, cfg) + evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) eg.Go(func() error { // Convert the transaction into an executable message and pre-cache its sender msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { return err // Also invalid block, bail out } - vmState.SetTxContext(tx.Hash(), i) - if err := precacheTransaction(msg, p.config, gaspool, vmState, header, evm); err != nil { + statedb.SetTxContext(tx.Hash(), i) + if results[i], err = precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { // NOTE: We don't care that the the transaction failed, we just want to pre-cache return err // Ugh, something went horribly wrong, bail out } @@ -111,18 +122,33 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c if err := eg.Wait(); err != nil { log.Error("Unexpected failure in pre-caching transactions", "err", err) } - *tape = vmState.tape + + // hack: just setting the gas used for now + receipts := make(types.Receipts, len(block.Transactions())) + for i, tx := range block.Transactions() { + receipts[i] = &types.Receipt{ + TxHash: tx.Hash(), + } + if results[i] != nil { + receipts[i].GasUsed = results[i].UsedGas + } + } + if err := p.engine.Finalize(p.bc, block, parent, statedb, receipts); err != nil { + log.Error("Failed to finalize block", "err", err) + } + + *tape = recorder.tape } // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb vm.StateDB, header *types.Header, evm *vm.EVM) error { +func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb vm.StateDB, header *types.Header, evm *vm.EVM) (*ExecutionResult, error) { // Update the evm with the new transaction context. evm.Reset(NewEVMTxContext(msg), statedb) // Add addresses to access list if applicable - _, err := ApplyMessage(evm, msg, gaspool) - return err + er, err := ApplyMessage(evm, msg, gaspool) + return er, err } type vmStateDB interface { @@ -134,94 +160,73 @@ type vmStateDB interface { GetLogs(common.Hash, uint64, common.Hash) []*types.Log } -type StateReadsRecorder struct { - vmStateDB +type snapRecorder struct { + snapshot.Snapshot - tape []byte + tape tape } -type StateReadsReplayer struct { - vmStateDB +type snapReplay struct { + snapshot.Snapshot - tape []byte + tape tape } -func (s *StateReadsRecorder) GetBalance(addr common.Address) *uint256.Int { - v := s.vmStateDB.GetBalance(addr) - bytes := v.Bytes() - s.tape = append(s.tape, byte(len(bytes))) - s.tape = append(s.tape, bytes...) - return v -} - -func (s *StateReadsReplayer) GetBalance(common.Address) *uint256.Int { - l := int(s.tape[0]) - v := new(uint256.Int) - v.SetBytes(s.tape[1 : 1+l]) - s.tape = s.tape[1+l:] - return v -} - -func (s *StateReadsRecorder) GetBalanceMultiCoin(addr common.Address, coin common.Hash) *big.Int { - v := s.vmStateDB.GetBalanceMultiCoin(addr, coin) - bytes := v.Bytes() - s.tape = append(s.tape, byte(len(bytes))) - s.tape = append(s.tape, v.Bytes()...) - return v -} - -func (s *StateReadsReplayer) GetBalanceMultiCoin(common.Address, common.Hash) *big.Int { - l := int(s.tape[0]) - v := new(big.Int) - v.SetBytes(s.tape[1 : 1+l]) - s.tape = s.tape[1+l:] - return v -} - -func (s *StateReadsRecorder) GetNonce(addr common.Address) uint64 { - v := s.vmStateDB.GetNonce(addr) - s.tape = binary.BigEndian.AppendUint64(s.tape, v) - return v -} - -func (s *StateReadsReplayer) GetNonce(common.Address) uint64 { - v := binary.BigEndian.Uint64(s.tape) - s.tape = s.tape[8:] - return v -} - -func (s *StateReadsRecorder) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - v := s.vmStateDB.GetCommittedState(addr, hash) - s.tape = append(s.tape, v.Bytes()...) - return v -} +func (s *snapRecorder) Account(accHash common.Hash) (*types.SlimAccount, error) { + acc, err := s.Snapshot.Account(accHash) + if err != nil { + return nil, err + } + if acc == nil { + // fmt.Println("nil account added") + s.tape = append(s.tape, 0) + return nil, nil + } -func (s *StateReadsReplayer) GetCommittedState(common.Address, common.Hash) common.Hash { - v := common.BytesToHash(s.tape[:32]) - s.tape = s.tape[32:] - return v + rlp, err := rlp.EncodeToBytes(acc) + if err != nil { + return nil, err + } + // fmt.Println("account added", len(rlp)) + s.tape = append(s.tape, byte(len(rlp))) + s.tape = append(s.tape, rlp...) + return acc, err } -func (s *StateReadsRecorder) GetCommittedStateAP1(addr common.Address, hash common.Hash) common.Hash { - v := s.vmStateDB.GetCommittedStateAP1(addr, hash) - s.tape = append(s.tape, v.Bytes()...) - return v -} +func (s *snapRecorder) Storage(accHash common.Hash, hash common.Hash) ([]byte, error) { + val, err := s.Snapshot.Storage(accHash, hash) + if err != nil { + return nil, err + } + // fmt.Println("storage added", len(val)) + s.tape = append(s.tape, byte(len(val))) + s.tape = append(s.tape, val...) + return val, nil +} + +func (s *snapReplay) Account(accHash common.Hash) (*types.SlimAccount, error) { + length := int(s.tape[0]) + s.tape = s.tape[1:] + if length == 0 { + // fmt.Println("nil account replayed") + return nil, nil + } -func (s *StateReadsReplayer) GetCommittedStateAP1(common.Address, common.Hash) common.Hash { - v := common.BytesToHash(s.tape[:32]) - s.tape = s.tape[32:] - return v + // fmt.Println("account replayed", length) + acc := new(types.SlimAccount) + if err := rlp.DecodeBytes(s.tape[:length], acc); err != nil { + return nil, err + } + s.tape = s.tape[length:] + return acc, nil } -func (s *StateReadsRecorder) GetState(addr common.Address, hash common.Hash) common.Hash { - v := s.vmStateDB.GetState(addr, hash) - s.tape = append(s.tape, v.Bytes()...) - return v -} +func (s *snapReplay) Storage(accHash common.Hash, hash common.Hash) ([]byte, error) { + length := int(s.tape[0]) + s.tape = s.tape[1:] + // fmt.Println("storage replayed", length) -func (s *StateReadsReplayer) GetState(common.Address, common.Hash) common.Hash { - v := common.BytesToHash(s.tape[:32]) - s.tape = s.tape[32:] - return v + val := s.tape[:length] + s.tape = s.tape[length:] + return val, nil } diff --git a/core/state_processor.go b/core/state_processor.go index 5a13c12611..43165cbda9 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -89,11 +89,6 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return nil, nil, 0, err } - var vmState vmStateDB = statedb - if p.tape != nil { - vmState = &StateReadsRecorder{vmStateDB: statedb, tape: p.tape} - } - var ( context = NewEVMBlockContext(header, p.bc, nil) vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) @@ -109,7 +104,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } statedb.SetTxContext(tx.Hash(), i) - receipt, err := applyTransaction(msg, p.config, gp, vmState, blockNumber, blockHash, tx, usedGas, vmenv) + receipt, err := applyTransaction(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index e44f5ba606..920fc3a506 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "math/big" "testing" "time" @@ -491,6 +492,7 @@ func TestReceiveWarpMessage(t *testing.T) { // Note each test corresponds to a block, the tests must be ordered by block // time and cannot, eg be run in parallel or a separate golang test. for _, test := range tests { + fmt.Println("Running test:", test.name) testReceiveWarpMessage( t, issuer, vm, test.sourceChainID, test.msgFrom, test.useSigners, test.blockTime, ) From db61d14769c7ae4d9e9be51ea912cdd0fdf81c81 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 18:02:42 -0800 Subject: [PATCH 019/307] add back reprocess --- core/blockchain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 8e41dd8a2f..22a304bb94 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -397,9 +397,9 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) - // if err := bc.reprocessFromGenesis(); err != nil { - // return nil, err - // } + if err := bc.reprocessFromGenesis(); err != nil { + return nil, err + } // Re-generate current block state if it is missing if err := bc.loadLastState(lastAcceptedHash); err != nil { From 0ba565f87082c6a63e6d77e58c1d0d5479663e2b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 19:32:37 -0800 Subject: [PATCH 020/307] include in replay --- core/blockchain.go | 29 ++++++++++++++++++----------- core/state_processor.go | 2 -- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 22a304bb94..49e8f25a19 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -33,6 +33,7 @@ import ( "fmt" "io" "math/big" + "os" "runtime" "strings" "sync" @@ -60,6 +61,12 @@ import ( "github.com/ethereum/go-ethereum/log" ) +var tapeDir string + +func init() { + tapeDir = os.Getenv("TAPE_DIR") +} + var ( accountReadTimer = metrics.NewRegisteredCounter("chain/account/reads", nil) accountHashTimer = metrics.NewRegisteredCounter("chain/account/hashes", nil) @@ -1702,29 +1709,30 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, err error parentRoot = parent.Root() ) + + // This will attempt to execute the block txs and create a tape. + // This is to avoid snapshot misses during execution. + tape := new(tape) + spStart := time.Now() + sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) + sp.Prefetch(current, parent.Root(), bc.vmConfig, tape) + spTime := time.Since(spStart) + // We don't simply use [NewWithSnapshot] here because it doesn't return an // error if [bc.snaps != nil] and [bc.snaps.Snapshot(parentRoot) == nil]. if bc.snaps == nil { statedb, err = state.New(parentRoot, bc.stateCache, nil) } else { snap := bc.snaps.Snapshot(parentRoot) + withReplay := &snapReplay{snap, *tape} if snap == nil { return common.Hash{}, fmt.Errorf("failed to get snapshot for parent root: %s", parentRoot) } - statedb, err = state.NewWithSnapshot(parentRoot, bc.stateCache, snap) + statedb, err = state.NewWithSnapshot(parentRoot, bc.stateCache, withReplay) } if err != nil { return common.Hash{}, fmt.Errorf("could not fetch state for (%s: %d): %v", parent.Hash().Hex(), parent.NumberU64(), err) } - - // This will attempt to execute the block txs in parallel. - // This is to avoid snapshot misses during execution. - tape := new(tape) - spStart := time.Now() - sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) - sp.Prefetch(current, parent.Root(), bc.vmConfig, tape) - spTime := time.Since(spStart) - // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain", bc.cacheConfig.TriePrefetcherParallelism) defer func() { @@ -1735,7 +1743,6 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() - bc.processor.(*StateProcessor).tape = *tape receipts, _, usedGas, err := bc.processor.Process(current, parent.Header(), statedb, vm.Config{}) if err != nil { return common.Hash{}, fmt.Errorf("failed to re-process block (%s: %d): %v", current.Hash().Hex(), current.NumberU64(), err) diff --git a/core/state_processor.go b/core/state_processor.go index 43165cbda9..efd1c1444c 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -51,8 +51,6 @@ type StateProcessor struct { config *params.ChainConfig // Chain configuration options bc *BlockChain // Canonical block chain engine consensus.Engine // Consensus engine used for block rewards - - tape tape } // NewStateProcessor initialises a new StateProcessor. From 08ebb3486afb87c960b9b63e132533470d89a08b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 20:06:09 -0800 Subject: [PATCH 021/307] add finalize --- core/state_prefetcher.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 920fc57370..8076d19497 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -143,11 +143,15 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb vm.StateDB, header *types.Header, evm *vm.EVM) (*ExecutionResult, error) { +func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb vmStateDB, header *types.Header, evm *vm.EVM) (*ExecutionResult, error) { // Update the evm with the new transaction context. evm.Reset(NewEVMTxContext(msg), statedb) // Add addresses to access list if applicable er, err := ApplyMessage(evm, msg, gaspool) + if err != nil { + return nil, err + } + statedb.Finalise(true) return er, err } From caf549a5673027b14d080a155d477b6575c8f50d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 10 Dec 2024 20:25:25 -0800 Subject: [PATCH 022/307] try off metrics expensive --- core/blockchain.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/blockchain.go b/core/blockchain.go index 49e8f25a19..f453b88cce 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1789,6 +1789,7 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, // reprocessFrom destroys the current snapshot (overrides it with genesis state) and // reprocesses the chain from the genesis block up to the current head block. func (bc *BlockChain) reprocessFromGenesis() error { + metrics.EnabledExpensive = false target := uint64(30_000_000) log.Warn("Reprocessing chain from genesis", "target", target) From 0a3d36d36d2fba0be0fdf85d38b0fb1f722df30b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 11 Dec 2024 05:42:13 -0800 Subject: [PATCH 023/307] check rtime --- core/blockchain.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index f453b88cce..b56cca2db5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1684,13 +1684,14 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e } type extraStats struct { - spTime time.Duration - pTime time.Duration - vTime time.Duration - cTime time.Duration - tapeLen uint64 - blocks uint64 - txs uint64 + spTime time.Duration + pTime time.Duration + vTime time.Duration + cTime time.Duration + readTime time.Duration + tapeLen uint64 + blocks uint64 + txs uint64 } // reprocessBlock reprocesses a previously accepted block. This is often used @@ -1740,6 +1741,8 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, }() // Process previously stored block + trieReadStart := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read + trieReadStart += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() @@ -1750,6 +1753,8 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, ptime := time.Since(pstart) accountMissEnd := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissEnd := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() + trieReadEnd := statedb.SnapshotAccountReads + statedb.AccountReads + trieReadEnd += statedb.SnapshotStorageReads + statedb.StorageReads snapshotCacheMissAccount.Inc(accountMissEnd - accountMissStart) snapshotCacheMissStorage.Inc(storageMissEnd - storageMissStart) @@ -1780,6 +1785,7 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block, stats.cTime += ctime stats.tapeLen += uint64(tape.Len()) stats.txs += uint64(len(current.Transactions())) + stats.readTime += trieReadEnd - trieReadStart stats.blocks++ } @@ -1834,11 +1840,12 @@ func (bc *BlockChain) reprocessFromGenesis() error { "Reprocessing chain", "block", i, "elapsed", common.PrettyDuration(time.Since(start).Truncate(time.Second)), - "flat", common.PrettyDuration(totalFlatTime.Truncate(time.Millisecond)), - "spTime", stats.spTime.Truncate(time.Millisecond), - "pTime", stats.pTime.Truncate(time.Millisecond), - "vTime", stats.vTime.Truncate(time.Millisecond), - "cTime", stats.cTime.Truncate(time.Millisecond), + "flat", common.PrettyDuration(totalFlatTime.Truncate(time.Second)), + "spTime", stats.spTime.Truncate(time.Second), + "pTime", stats.pTime.Truncate(time.Second), + "vTime", stats.vTime.Truncate(time.Second), + "cTime", stats.cTime.Truncate(time.Second), + "readTime", stats.readTime.Truncate(time.Millisecond), "accountMiss", accountMiss, "storageMiss", storageMiss, "tapeLen", stats.tapeLen, From a3b591812f5f553a78d09a9a8e3e4ce638f28a0e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 13 Dec 2024 12:21:55 -0800 Subject: [PATCH 024/307] shim layers for trie interception / use of kv backend: "stuff working" --- core/block_validator.go | 24 ++-- core/blockchain.go | 67 +++++++--- core/state/database.go | 31 +++++ core/state/statedb.go | 3 + core/state_manager.go | 25 ++-- plugin/evm/reprocess_test.go | 131 ++++++++++++++++++++ plugin/evm/vm.go | 1 + shim/geth_backend.go | 53 ++++++++ shim/hasher.go | 65 ++++++++++ shim/kv_backend.go | 87 +++++++++++++ shim/merkledb/merkledb.go | 77 ++++++++++++ shim/secure_trie.go | 231 +++++++++++++++++++++++++++++++++++ shim/trie.go | 97 +++++++++++++++ triedb/database.go | 45 ++++++- 14 files changed, 896 insertions(+), 41 deletions(-) create mode 100644 plugin/evm/reprocess_test.go create mode 100644 shim/geth_backend.go create mode 100644 shim/hasher.go create mode 100644 shim/kv_backend.go create mode 100644 shim/merkledb/merkledb.go create mode 100644 shim/secure_trie.go create mode 100644 shim/trie.go diff --git a/core/block_validator.go b/core/block_validator.go index a75eeb01a1..3a5b42f443 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/trie" + "github.com/ethereum/go-ethereum/common" ) // BlockValidator is responsible for validating block headers, uncles and @@ -45,14 +46,17 @@ type BlockValidator struct { config *params.ChainConfig // Chain configuration options bc *BlockChain // Canonical block chain engine consensus.Engine // Consensus engine used for validating + + CheckRoot func(expected, got common.Hash) bool } // NewBlockValidator returns a new block validator which is safe for re-use func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engine consensus.Engine) *BlockValidator { validator := &BlockValidator{ - config: config, - engine: engine, - bc: blockchain, + config: config, + engine: engine, + bc: blockchain, + CheckRoot: func(expected, got common.Hash) bool { return expected == got }, } return validator } @@ -106,12 +110,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } // Ancestor block must be known. - if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { - if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { - return consensus.ErrUnknownAncestor - } - return consensus.ErrPrunedAncestor - } + // if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { + // if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { + // return consensus.ErrUnknownAncestor + // } + // return consensus.ErrPrunedAncestor + // } return nil } @@ -135,7 +139,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD } // Validate the state root against the received state root and throw // an error if they don't match. - if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); !v.CheckRoot(header.Root, root) { return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) } return nil diff --git a/core/blockchain.go b/core/blockchain.go index b56cca2db5..ae8dd0faaf 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -179,6 +179,8 @@ type CacheConfig struct { StateHistory uint64 // Number of blocks from head whose state histories are reserved. StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top + KeyValueDB *triedb.KeyValueConfig // Config for key value db + SnapshotNoBuild bool // Whether the background generation is allowed SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } @@ -200,6 +202,7 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { DirtyCacheSize: c.TrieDirtyLimit * 1024 * 1024, } } + config.KeyValueDB = c.KeyValueDB return config } @@ -233,6 +236,13 @@ type txLookup struct { transaction *types.Transaction } +type blockRoot struct { + *types.Block + root common.Hash +} + +func (b *blockRoot) Root() common.Hash { return b.root } + // BlockChain represents the canonical chain given a database with a genesis // block. The Blockchain manages chain imports, reverts, chain reorganisations. // @@ -298,7 +308,7 @@ type BlockChain struct { // different than [chainAcceptedFeed], which is sent an event after an accepted // block is processed (after each loop of the accepted worker). If there is a // clean shutdown, all items inserted into the [acceptorQueue] will be processed. - acceptorQueue chan *types.Block + acceptorQueue chan *blockRoot // [acceptorClosingLock], and [acceptorClosed] are used // to synchronize the closing of the [acceptorQueue] channel. @@ -382,7 +392,7 @@ func NewBlockChain( engine: engine, vmConfig: vmConfig, senderCacher: NewTxSenderCacher(runtime.NumCPU()), - acceptorQueue: make(chan *types.Block, cacheConfig.AcceptorQueueLimit), + acceptorQueue: make(chan *blockRoot, cacheConfig.AcceptorQueueLimit), quit: make(chan struct{}), acceptedLogsCache: NewFIFOCache[common.Hash, [][]*types.Log](cacheConfig.AcceptedCacheSize), } @@ -404,9 +414,9 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) - if err := bc.reprocessFromGenesis(); err != nil { - return nil, err - } + // if err := bc.reprocessFromGenesis(); err != nil { + // return nil, err + // } // Re-generate current block state if it is missing if err := bc.loadLastState(lastAcceptedHash); err != nil { @@ -568,6 +578,7 @@ func (bc *BlockChain) startAcceptor() { log.Crit("unable to flatten snapshot from acceptor", "blockHash", next.Hash(), "err", err) } + next := next.Block // Update last processed and transaction lookup index if err := bc.writeBlockAcceptedIndices(next); err != nil { log.Crit("failed to write accepted block effects", "err", err) @@ -606,7 +617,7 @@ func (bc *BlockChain) startAcceptor() { // addAcceptorQueue adds a new *types.Block to the [acceptorQueue]. This will // block if there are [AcceptorQueueLimit] items in [acceptorQueue]. -func (bc *BlockChain) addAcceptorQueue(b *types.Block) { +func (bc *BlockChain) addAcceptorQueue(b *blockRoot) { // We only acquire a read lock here because it is ok to add items to the // [acceptorQueue] concurrently. bc.acceptorClosingLock.RLock() @@ -722,6 +733,11 @@ func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error { return bc.reprocessState(bc.lastAccepted, 2*bc.cacheConfig.CommitInterval) } +func (bc *BlockChain) LoadGenesisState(block *types.Block) error { + bc.genesisBlock = block + return bc.loadGenesisState() +} + func (bc *BlockChain) loadGenesisState() error { // Prepare the genesis block and reinitialise the chain batch := bc.db.NewBatch() @@ -783,6 +799,13 @@ func (bc *BlockChain) ExportCallback(callback func(block *types.Block) error, fi return nil } +func (bc *BlockChain) WriteHeadBlock(block *types.Block) { + bc.chainmu.Lock() + defer bc.chainmu.Unlock() + + bc.writeHeadBlock(block) +} + // writeHeadBlock injects a new head block into the current block chain. This method // assumes that the block is indeed a true head. It will also reset the head // header to this very same block if they are older or if they are on a different side chain. @@ -1044,6 +1067,10 @@ func (bc *BlockChain) LastAcceptedBlock() *types.Block { // // Assumes [bc.chainmu] is not held by the caller. func (bc *BlockChain) Accept(block *types.Block) error { + return bc.AcceptWithRoot(block, block.Root()) +} + +func (bc *BlockChain) AcceptWithRoot(block *types.Block, root common.Hash) error { bc.chainmu.Lock() defer bc.chainmu.Unlock() @@ -1070,7 +1097,7 @@ func (bc *BlockChain) Accept(block *types.Block) error { // Enqueue block in the acceptor bc.lastAccepted = block - bc.addAcceptorQueue(block) + bc.addAcceptorQueue(&blockRoot{Block: block, root: root}) acceptedBlockGasUsedCounter.Inc(int64(block.GasUsed())) acceptedTxsCounter.Inc(int64(len(block.Transactions()))) return nil @@ -1239,7 +1266,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { bc.chainmu.Lock() defer bc.chainmu.Unlock() for n, block := range chain { - if err := bc.insertBlock(block, true); err != nil { + if err := bc.insertBlock(block, nil, true); err != nil { return n, err } } @@ -1252,17 +1279,21 @@ func (bc *BlockChain) InsertBlock(block *types.Block) error { } func (bc *BlockChain) InsertBlockManual(block *types.Block, writes bool) error { + return bc.InsertBlockManualWithParent(block, nil, writes) +} + +func (bc *BlockChain) InsertBlockManualWithParent(block *types.Block, parent *types.Header, writes bool) error { bc.blockProcFeed.Send(true) defer bc.blockProcFeed.Send(false) bc.chainmu.Lock() - err := bc.insertBlock(block, writes) + err := bc.insertBlock(block, parent, writes) bc.chainmu.Unlock() return err } -func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { +func (bc *BlockChain) insertBlock(block *types.Block, parent *types.Header, writes bool) error { start := time.Now() bc.senderCacher.Recover(types.MakeSigner(bc.chainConfig, block.Number(), block.Time()), block.Transactions()) @@ -1312,8 +1343,10 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { } }() - // Retrieve the parent block to determine which root to build state on - parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + // Retrieve the parent block to determine which root to build state on + parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + } // Instantiate the statedb to use for processing transactions // @@ -1323,16 +1356,16 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { defer bc.flattenLock.Unlock() // This will attempt to execute the block txs in parallel. // This is to avoid snapshot misses during execution. - sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) - tape := new(tape) - sp.Prefetch(block, parent.Root, bc.vmConfig, tape) + // sp := newStatePrefetcher(bc.chainConfig, bc, bc.engine) + // tape := new(tape) + // sp.Prefetch(block, parent.Root, bc.vmConfig, tape) substart = time.Now() var statedb *state.StateDB if bc.snaps != nil { snap := bc.snaps.Snapshot(parent.Root) - withReplay := &snapReplay{snap, *tape} - //withReplay := snap + // withReplay := &snapReplay{snap, *tape} + withReplay := snap statedb, err = state.NewWithSnapshot(parent.Root, bc.stateCache, withReplay) } else { statedb, err = state.New(parent.Root, bc.stateCache, nil) diff --git a/core/state/database.go b/core/state/database.go index e29e9b8d78..413dc73b92 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -32,6 +32,7 @@ import ( "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/shim" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/trie/utils" @@ -189,6 +190,19 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { + if kvConfig := db.triedb.Config().KeyValueDB; kvConfig != nil { + if kvConfig.KVBackend != nil { + return shim.NewAccountTrieKV(root, kvConfig.KVBackend, db.triedb) + } + // Legacy backend maintains hash compatibility with geth + // to test the shim layer. + backend, err := shim.NewLegacyBackend(root, common.Hash{}, root, db.triedb) + if err != nil { + return nil, err + } + return shim.NewStateTrie(backend, db.triedb), nil + } + if db.triedb.IsVerkle() { return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) } @@ -201,6 +215,21 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { // OpenStorageTrie opens the storage trie of an account. func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { + if kvConfig := db.triedb.Config().KeyValueDB; kvConfig != nil { + addrHash := crypto.Keccak256Hash(address.Bytes()) + + if kvConfig.KVBackend != nil { + accountTrie := self.(*shim.StateTrie) + return shim.NewStorageTrieKV(stateRoot, addrHash, accountTrie) + } + // Legacy backend maintains hash compatibility with geth + // to test the shim layer. + backend, err := shim.NewLegacyBackend(stateRoot, addrHash, root, db.triedb) + if err != nil { + return nil, err + } + return shim.NewStateTrie(backend, db.triedb), nil + } // In the verkle case, there is only one tree. But the two-tree structure // is hardcoded in the codebase. So we need to return the same trie in this // case. @@ -217,6 +246,8 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre // CopyTrie returns an independent copy of the given trie. func (db *cachingDB) CopyTrie(t Trie) Trie { switch t := t.(type) { + case *shim.StateTrie: + return t.Copy() case *trie.StateTrie: return t.Copy() default: diff --git a/core/state/statedb.go b/core/state/statedb.go index 3096589b6e..dce72992d3 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -209,6 +209,9 @@ func (s *StateDB) StartPrefetcher(namespace string, maxConcurrency int) { s.prefetcher.close() s.prefetcher = nil } + if maxConcurrency == 0 { + return // No prefetching + } if s.snap != nil { s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) } diff --git a/core/state_manager.go b/core/state_manager.go index 2057672e98..312eab4751 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -31,7 +31,6 @@ import ( "math/rand" "time" - "github.com/ava-labs/coreth/core/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -59,9 +58,9 @@ const ( ) type TrieWriter interface { - InsertTrie(block *types.Block) error // Handle inserted trie reference of [root] - AcceptTrie(block *types.Block) error // Mark [root] as part of an accepted block - RejectTrie(block *types.Block) error // Notify TrieWriter that the block containing [root] has been rejected + InsertTrie(block Block) error // Handle inserted trie reference of [root] + AcceptTrie(block Block) error // Mark [root] as part of an accepted block + RejectTrie(block Block) error // Notify TrieWriter that the block containing [root] has been rejected Shutdown() error } @@ -91,23 +90,29 @@ func NewTrieWriter(db TrieDB, config *CacheConfig) TrieWriter { } } +type Block interface { + Root() common.Hash + Hash() common.Hash + NumberU64() uint64 +} + type noPruningTrieWriter struct { TrieDB } -func (np *noPruningTrieWriter) InsertTrie(block *types.Block) error { +func (np *noPruningTrieWriter) InsertTrie(block Block) error { // We don't attempt to [Cap] here because we should never have // a significant amount of [TrieDB.Dirties] (we commit each block). return nil } -func (np *noPruningTrieWriter) AcceptTrie(block *types.Block) error { +func (np *noPruningTrieWriter) AcceptTrie(block Block) error { // We don't need to call [Dereference] on the block root at the end of this // function because it is removed from the [TrieDB.Dirties] map in [Commit]. return np.TrieDB.Commit(block.Root(), false) } -func (np *noPruningTrieWriter) RejectTrie(block *types.Block) error { +func (np *noPruningTrieWriter) RejectTrie(block Block) error { return np.TrieDB.Dereference(block.Root()) } @@ -124,7 +129,7 @@ type cappedMemoryTrieWriter struct { tipBuffer *BoundedBuffer[common.Hash] } -func (cm *cappedMemoryTrieWriter) InsertTrie(block *types.Block) error { +func (cm *cappedMemoryTrieWriter) InsertTrie(block Block) error { // The use of [Cap] in [InsertTrie] prevents exceeding the configured memory // limit (and OOM) in case there is a large backlog of processing (unaccepted) blocks. _, nodes, imgs := cm.TrieDB.Size() // all memory is contained within the nodes return for hashdb @@ -139,7 +144,7 @@ func (cm *cappedMemoryTrieWriter) InsertTrie(block *types.Block) error { return nil } -func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { +func (cm *cappedMemoryTrieWriter) AcceptTrie(block Block) error { root := block.Root() // Attempt to dereference roots at least [tipBufferSize] old (so queries at tip @@ -187,7 +192,7 @@ func (cm *cappedMemoryTrieWriter) AcceptTrie(block *types.Block) error { return nil } -func (cm *cappedMemoryTrieWriter) RejectTrie(block *types.Block) error { +func (cm *cappedMemoryTrieWriter) RejectTrie(block Block) error { cm.TrieDB.Dereference(block.Root()) return nil } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go new file mode 100644 index 0000000000..c7582e68e5 --- /dev/null +++ b/plugin/evm/reprocess_test.go @@ -0,0 +1,131 @@ +package evm + +import ( + "context" + "math/big" + "testing" + + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/avalanchego/utils/units" + xmerkledb "github.com/ava-labs/avalanchego/x/merkledb" + "github.com/ava-labs/coreth/consensus/dummy" + "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/rawdb" + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/core/vm" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/shim/merkledb" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" +) + +func TestReprocessGenesis(t *testing.T) { + chainConfig := params.TestChainConfig + testVM := &VM{ + chainConfig: chainConfig, + codec: Codec, + ctx: &snow.Context{ + AVAXAssetID: ids.ID{1}, + }, + } + key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 := crypto.PubkeyToAddress(key1.PublicKey) + db := rawdb.NewMemoryDatabase() + g := &core.Genesis{ + Config: chainConfig, + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, + } + cbs := dummy.ConsensusCallbacks{ + OnExtraStateChange: testVM.onExtraStateChange, + } + ctx := context.Background() + + mdbKVStore := memdb.New() + mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ + BranchFactor: xmerkledb.BranchFactor16, + Hasher: xmerkledb.DefaultHasher, + HistoryLength: 1, + RootGenConcurrency: 0, + ValueNodeCacheSize: units.MiB, + IntermediateNodeCacheSize: units.MiB, + IntermediateWriteBufferSize: units.KiB, + IntermediateWriteBatchSize: 256 * units.KiB, + Reg: prometheus.NewRegistry(), + TraceLevel: xmerkledb.InfoTrace, + Tracer: trace.Noop, + }) + require.NoError(t, err) + + engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ + ModeSkipHeader: true, + }) + cacheConfig := *core.DefaultCacheConfig + cacheConfig.KeyValueDB = &triedb.KeyValueConfig{ + KVBackend: merkledb.NewMerkleDB(mdb), + } + cacheConfig.TriePrefetcherParallelism = 4 + cacheConfig.SnapshotLimit = 0 + cacheConfig.Pruning = false + + bc, err := core.NewBlockChain(db, &cacheConfig, g, engine, vm.Config{}, common.Hash{}, false) + require.NoError(t, err) + + var lastInsertedRoot common.Hash + bc.Validator().(*core.BlockValidator).CheckRoot = func(expected, got common.Hash) bool { + t.Logf("Got root: %s", got.Hex()) + lastInsertedRoot = got + return true + } + + normalGenesis := g.ToBlock() + // rawdb.WriteHeader(db, normalGenesis.Header()) + // bc.WriteHeadBlock(normalGenesis) + require.NoError(t, bc.LoadGenesisState(normalGenesis)) + + t.Logf("Genesis block: %s", bc.CurrentBlock().Hash().Hex()) + getCurrentRoot := func() common.Hash { + return cacheConfig.KeyValueDB.KVBackend.Root() + } + lastRoot := getCurrentRoot() + + // Let's generate some blocks + signer := types.LatestSigner(chainConfig) + _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, 10, 2, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + GasPrice: b.BaseFee(), + Gas: 21000, + To: &addr1, + }), signer, key1) + b.AddTx(tx) + }) + require.NoError(t, err) + t.Logf("Generated %d blocks", len(blocks)) + for _, block := range blocks { + t.Logf("Transactions: %d, Parent State: %x", len(block.Transactions()), lastRoot) + + // Override parentRoot to match last state + parent := bc.GetHeaderByNumber(block.NumberU64() - 1) + originalParentRoot := parent.Root + parent.Root = lastRoot + + err := bc.InsertBlockManualWithParent(block, parent, true) + require.NoError(t, err) + + // Restore parent root + parent.Root = originalParentRoot + + err = bc.AcceptWithRoot(block, lastInsertedRoot) + require.NoError(t, err) + + bc.DrainAcceptorQueue() + + lastRoot = getCurrentRoot() + } +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 8cc8956265..5cfbb6b5a0 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -505,6 +505,7 @@ func (vm *VM) Initialize( vm.ethConfig = ethconfig.NewDefaultConfig() vm.ethConfig.Genesis = g + log.Info("Genesis bytes", "bytes", genesisBytes) vm.ethConfig.NetworkId = vm.chainID.Uint64() vm.genesisHash = vm.ethConfig.Genesis.ToBlock().Hash() // must create genesis hash before [vm.readLastAccepted] lastAcceptedHash, lastAcceptedHeight, err := vm.readLastAccepted() diff --git a/shim/geth_backend.go b/shim/geth_backend.go new file mode 100644 index 0000000000..1fb4ed1a79 --- /dev/null +++ b/shim/geth_backend.go @@ -0,0 +1,53 @@ +package shim + +import ( + "github.com/ava-labs/coreth/trie" + "github.com/ava-labs/coreth/trie/trienode" + "github.com/ava-labs/coreth/triedb/database" + "github.com/ethereum/go-ethereum/common" +) + +var _ Backend = (*LegacyBackend)(nil) + +type LegacyBackend struct { + hash common.Hash + hashed bool + tr *trie.Trie +} + +func NewLegacyBackend( + stateRoot common.Hash, addrHash common.Hash, root common.Hash, db database.Database, +) (*LegacyBackend, error) { + trieID := trie.StateTrieID(root) + if addrHash != (common.Hash{}) { + trieID = trie.StorageTrieID(stateRoot, addrHash, root) + } + + tr, err := trie.New(trieID, db) + if err != nil { + return nil, err + } + + return &LegacyBackend{tr: tr}, nil +} + +func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } + +func (b *LegacyBackend) Hash(batch Batch) common.Hash { + if b.hashed { + return b.hash + } + for _, kv := range batch { + b.tr.MustUpdate(kv.Key, kv.Value) + } + b.hashed = true + b.hash = b.tr.Hash() + return b.hash +} + +func (b *LegacyBackend) Commit(batch Batch, collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { + if !b.hashed { + b.Hash(batch) + } + return b.tr.Commit(collectLeaf) +} diff --git a/shim/hasher.go b/shim/hasher.go new file mode 100644 index 0000000000..7446d6e777 --- /dev/null +++ b/shim/hasher.go @@ -0,0 +1,65 @@ +// (c) 2020-2021, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package shim + +import ( + "sync" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// hasher is a type used for the trie Hash operation. A hasher has some +// internal preallocated temp space +type hasher struct { + sha crypto.KeccakState + tmp []byte + encbuf rlp.EncoderBuffer + parallel bool // Whether to use parallel threads when hashing +} + +// hasherPool holds pureHashers +var hasherPool = sync.Pool{ + New: func() interface{} { + return &hasher{ + tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. + sha: sha3.NewLegacyKeccak256().(crypto.KeccakState), + encbuf: rlp.NewEncoderBuffer(nil), + } + }, +} + +func newHasher(parallel bool) *hasher { + h := hasherPool.Get().(*hasher) + h.parallel = parallel + return h +} + +func returnHasherToPool(h *hasher) { + hasherPool.Put(h) +} diff --git a/shim/kv_backend.go b/shim/kv_backend.go new file mode 100644 index 0000000000..5aa0947e15 --- /dev/null +++ b/shim/kv_backend.go @@ -0,0 +1,87 @@ +package shim + +import ( + "errors" + "fmt" + + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/trie/trienode" + "github.com/ava-labs/coreth/triedb" + "github.com/ava-labs/coreth/triedb/database" + "github.com/ethereum/go-ethereum/common" +) + +var ( + ErrRootMismatch = errors.New("root mismatch") + ErrStorageStateRootMismatch = errors.New("storage state root mismatch") +) + +type ( + KV = triedb.KV + Batch = triedb.Batch + KVBackend = triedb.KVBackend +) + +type KVTrieBackend struct { + hashed bool + hash common.Hash + backend KVBackend +} + +func (k *KVTrieBackend) Get(key []byte) ([]byte, error) { + return k.backend.Get(key) +} + +func (k *KVTrieBackend) Hash(batch Batch) common.Hash { + if k.hashed { + return k.hash + } + root, err := k.backend.Update(batch) + if err != nil { + panic(fmt.Sprintf("failed to update trie: %v", err)) + } + k.hashed = true + k.hash = root + return root +} + +func (k *KVTrieBackend) Commit(batch Batch, collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { + if !k.hashed { + k.Hash(batch) + } + return k.hash, nil, nil +} + +// NewAccountTrieKV creates a new state trie backed by a key-value store. +// db is used for preimages +func NewAccountTrieKV(stateRoot common.Hash, kv KVBackend, db database.Database) (*StateTrie, error) { + if stateRoot == types.EmptyRootHash { + stateRoot = common.Hash{} + } + kvRoot := kv.Root() + if kvRoot != stateRoot { + return nil, fmt.Errorf("%w: expected %x, got %x", ErrRootMismatch, stateRoot, kvRoot) + } + + tr := Trie{ + backend: &KVTrieBackend{backend: kv}, + origin: kvRoot, + } + return &StateTrie{trie: tr, db: db}, nil +} + +// NewStorageTrieKV creates a new storage trie backed by a key-value store. +func NewStorageTrieKV(stateRoot common.Hash, account common.Hash, accountTrie *StateTrie) (*StateTrie, error) { + if stateRoot == types.EmptyRootHash { + stateRoot = common.Hash{} + } + if accountTrie.trie.origin != stateRoot { + return nil, fmt.Errorf("%w: expected %x, got %x", ErrStorageStateRootMismatch, stateRoot, accountTrie.trie.origin) + } + tr := Trie{ + parent: &accountTrie.trie, + backend: accountTrie.trie.backend, + prefix: account.Bytes(), + } + return &StateTrie{trie: tr, db: accountTrie.db}, nil +} diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go new file mode 100644 index 0000000000..88d4dfcee1 --- /dev/null +++ b/shim/merkledb/merkledb.go @@ -0,0 +1,77 @@ +package merkledb + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/x/merkledb" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" +) + +var _ triedb.KVBackend = &MerkleDB{} + +type MerkleDB struct { + db merkledb.MerkleDB + pendingView merkledb.View + pendingViewRoot common.Hash +} + +func NewMerkleDB(db merkledb.MerkleDB) *MerkleDB { + return &MerkleDB{db: db} +} + +func (m *MerkleDB) Get(key []byte) ([]byte, error) { + val, err := m.db.Get(key) + if err == database.ErrNotFound { + return nil, nil + } + return val, err +} + +func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { + ctx := context.TODO() + changes := make([]database.BatchOp, len(batch)) + for i, kv := range batch { + changes[i] = database.BatchOp{ + Key: kv.Key, + Value: kv.Value, + Delete: len(kv.Value) == 0, + } + } + view, err := m.db.NewView(ctx, merkledb.ViewChanges{BatchOps: changes}) + if err != nil { + return common.Hash{}, err + } + root, err := view.GetMerkleRoot(ctx) + if err != nil { + return common.Hash{}, err + } + m.pendingView = view + m.pendingViewRoot = common.Hash(root) + return common.Hash(root), nil +} + +func (m *MerkleDB) Commit(root common.Hash) error { + if m.pendingViewRoot != root { + return fmt.Errorf("root mismatch: expected %x, got %x", root, m.pendingViewRoot) + } + ctx := context.TODO() + if err := m.pendingView.CommitToDB(ctx); err != nil { + return err + } + m.pendingView = nil + m.pendingViewRoot = common.Hash{} + fmt.Printf("Commit: %x\n", root) + return nil +} + +func (m *MerkleDB) Root() common.Hash { + ctx := context.TODO() + root, err := m.db.GetMerkleRoot(ctx) + if err != nil { + panic(fmt.Sprintf("failed to get merkle root: %v", err)) + } + return common.Hash(root) +} diff --git a/shim/secure_trie.go b/shim/secure_trie.go new file mode 100644 index 0000000000..25f9810aa8 --- /dev/null +++ b/shim/secure_trie.go @@ -0,0 +1,231 @@ +// (c) 2020-2021, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package shim + +import ( + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/trie" + "github.com/ava-labs/coreth/trie/trienode" + "github.com/ava-labs/coreth/triedb/database" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" +) + +// StateTrie wraps a trie with key hashing. In a stateTrie trie, all +// access operations hash the key using keccak256. This prevents +// calling code from creating long chains of nodes that +// increase the access time. +// +// Contrary to a regular trie, a StateTrie can only be created with +// New and must have an attached database. The database also stores +// the preimage of each key if preimage recording is enabled. +// +// StateTrie is not safe for concurrent use. +type StateTrie struct { + trie Trie + db database.Database + hashKeyBuf [common.HashLength]byte + secKeyCache map[string][]byte + secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch +} + +// GetStorage attempts to retrieve a storage slot with provided account address +// and slot key. The value bytes must not be modified by the caller. +// If the specified storage slot is not in the trie, nil will be returned. +// If a trie node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { + enc, err := t.trie.Get(t.hashKey(key)) + if err != nil || len(enc) == 0 { + return nil, err + } + _, content, _, err := rlp.Split(enc) + return content, err +} + +// GetAccount attempts to retrieve an account with provided account address. +// If the specified account is not in the trie, nil will be returned. +// If a trie node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, error) { + res, err := t.trie.Get(t.hashKey(address.Bytes())) + if res == nil || err != nil { + return nil, err + } + ret := new(types.StateAccount) + err = rlp.DecodeBytes(res, ret) + return ret, err +} + +// GetAccountByHash does the same thing as GetAccount, however it expects an +// account hash that is the hash of address. This constitutes an abstraction +// leak, since the client code needs to know the key format. +func (t *StateTrie) GetAccountByHash(addrHash common.Hash) (*types.StateAccount, error) { + res, err := t.trie.Get(addrHash.Bytes()) + if res == nil || err != nil { + return nil, err + } + ret := new(types.StateAccount) + err = rlp.DecodeBytes(res, ret) + return ret, err +} + +// UpdateStorage associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +// +// If a node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { + hk := t.hashKey(key) + v, _ := rlp.EncodeToBytes(value) + err := t.trie.Update(hk, v) + if err != nil { + return err + } + t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) + return nil +} + +// UpdateAccount will abstract the write of an account to the secure trie. +func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error { + hk := t.hashKey(address.Bytes()) + data, err := rlp.EncodeToBytes(acc) + if err != nil { + return err + } + if err := t.trie.Update(hk, data); err != nil { + return err + } + t.getSecKeyCache()[string(hk)] = address.Bytes() + return nil +} + +func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { + return nil +} + +// DeleteStorage removes any existing storage slot from the trie. +// If the specified trie node is not in the trie, nothing will be changed. +// If a node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error { + hk := t.hashKey(key) + delete(t.getSecKeyCache(), string(hk)) + return t.trie.Delete(hk) +} + +// DeleteAccount abstracts an account deletion from the trie. +func (t *StateTrie) DeleteAccount(address common.Address) error { + hk := t.hashKey(address.Bytes()) + delete(t.getSecKeyCache(), string(hk)) + return t.trie.Delete(hk) +} + +// GetKey returns the sha3 preimage of a hashed key that was +// previously used to store a value. +func (t *StateTrie) GetKey(shaKey []byte) []byte { + if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { + return key + } + return t.db.Preimage(common.BytesToHash(shaKey)) +} + +// Commit collects all dirty nodes in the trie and replaces them with the +// corresponding node hash. All collected nodes (including dirty leaves if +// collectLeaf is true) will be encapsulated into a nodeset for return. +// The returned nodeset can be nil if the trie is clean (nothing to commit). +// All cached preimages will be also flushed if preimages recording is enabled. +// Once the trie is committed, it's not usable anymore. A new trie must +// be created with new root and updated trie database for following usage +func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { + // Write all the pre-images to the actual disk database + if len(t.getSecKeyCache()) > 0 { + preimages := make(map[common.Hash][]byte) + for hk, key := range t.secKeyCache { + preimages[common.BytesToHash([]byte(hk))] = key + } + t.db.InsertPreimage(preimages) + t.secKeyCache = make(map[string][]byte) + } + // Commit the trie and return its modified nodeset. + return t.trie.Commit(collectLeaf) +} + +// Hash returns the root hash of StateTrie. It does not write to the +// database and can be used even if the trie doesn't have one. +func (t *StateTrie) Hash() common.Hash { + return t.trie.Hash() +} + +// Copy returns a copy of StateTrie. +func (t *StateTrie) Copy() *StateTrie { + return &StateTrie{ + trie: *t.trie.Copy(), + db: t.db, + secKeyCache: t.secKeyCache, + } +} + +// NodeIterator returns an iterator that returns nodes of the underlying trie. +// Iteration starts at the key after the given start key. +func (t *StateTrie) NodeIterator(start []byte) (trie.NodeIterator, error) { + return t.trie.NodeIterator(start) +} + +// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered +// error but just print out an error message. +func (t *StateTrie) MustNodeIterator(start []byte) trie.NodeIterator { + return t.trie.MustNodeIterator(start) +} + +// hashKey returns the hash of key as an ephemeral buffer. +// The caller must not hold onto the return value because it will become +// invalid on the next call to hashKey or secKey. +func (t *StateTrie) hashKey(key []byte) []byte { + h := newHasher(false) + h.sha.Reset() + h.sha.Write(key) + h.sha.Read(t.hashKeyBuf[:]) + returnHasherToPool(h) + return t.hashKeyBuf[:] +} + +// getSecKeyCache returns the current secure key cache, creating a new one if +// ownership changed (i.e. the current secure trie is a copy of another owning +// the actual cache). +func (t *StateTrie) getSecKeyCache() map[string][]byte { + if t != t.secKeyCacheOwner { + t.secKeyCacheOwner = t + t.secKeyCache = make(map[string][]byte) + } + return t.secKeyCache +} + +func (t *StateTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + return t.trie.Prove(key, proofDb) +} diff --git a/shim/trie.go b/shim/trie.go new file mode 100644 index 0000000000..efc0df8fd2 --- /dev/null +++ b/shim/trie.go @@ -0,0 +1,97 @@ +package shim + +import ( + "bytes" + "fmt" + + "github.com/ava-labs/coreth/trie" + "github.com/ava-labs/coreth/trie/trienode" + "github.com/ava-labs/coreth/triedb/database" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +type Trie struct { + changes Batch + backend Backend + + prefix []byte + parent *Trie + origin common.Hash +} + +type Backend interface { + Get(key []byte) ([]byte, error) + Hash(batch Batch) common.Hash + Commit(batch Batch, collectLeaf bool) (common.Hash, *trienode.NodeSet, error) +} + +func NewStateTrie(backend Backend, db database.Database) *StateTrie { + return &StateTrie{ + trie: Trie{backend: backend}, + db: db, + } +} + +func (t *Trie) getKey(key []byte) []byte { + return append(t.prefix, key...) +} + +// Update batches updates to the trie +func (t *Trie) Update(key, value []byte) error { + fmt.Printf("Update: %x -> %x\n", key, value) + key = t.getKey(key) + value = bytes.Clone(value) + if t.parent != nil { + t.parent.changes = append(t.parent.changes, KV{Key: key, Value: value}) + } else { + t.changes = append(t.changes, KV{Key: key, Value: value}) + } + return nil +} + +func (t *Trie) Delete(key []byte) error { + fmt.Printf("Delete: %x\n", key) + key = t.getKey(key) + if t.parent != nil { + t.parent.changes = append(t.parent.changes, KV{Key: key}) + } else { + t.changes = append(t.changes, KV{Key: key}) + } + return nil +} + +func (t *Trie) Hash() common.Hash { + if t.parent != nil { + return common.Hash{} + } + + fmt.Printf("Hashing %d changes\n", len(t.changes)) + return t.backend.Hash(t.changes) +} + +func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { + if t.parent != nil { + return common.Hash{}, nil, nil + } + + fmt.Printf("Committing %d changes\n", len(t.changes)) + return t.backend.Commit(t.changes, collectLeaf) +} + +func (t *Trie) Get(key []byte) ([]byte, error) { + fmt.Printf("Get: %x\n", key) + key = t.getKey(key) + return t.backend.Get(key) +} + +func (t *Trie) Copy() *Trie { + fmt.Printf("Copy requested (%d changes): %p\n", len(t.changes), t) + changes := make(Batch, len(t.changes)) + copy(changes, t.changes) + return &Trie{backend: t.backend, changes: changes} +} + +func (t *Trie) NodeIterator(start []byte) (trie.NodeIterator, error) { panic("not implemented") } +func (t *Trie) MustNodeIterator(start []byte) trie.NodeIterator { panic("not implemented") } +func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { panic("not implemented") } diff --git a/triedb/database.go b/triedb/database.go index 7421b74cf0..07f7132c12 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -30,12 +30,40 @@ import ( "github.com/ethereum/go-ethereum/log" ) +type KV struct { + Key []byte + Value []byte +} + +type Batch []KV + +type KVBackend interface { + // Returns the current root hash of the trie. + // Empty trie must return common.Hash{}. + Root() common.Hash + + // Get retrieves the value for the given key. + Get(key []byte) ([]byte, error) + + // After this call, Root() should return the same hash as returned by this call. + Update(Batch) (common.Hash, error) + + // After this call, changes related to [root] should be persisted to disk. + // This may be implemented as no-op if Update already persists changes. + Commit(root common.Hash) error +} + +type KeyValueConfig struct { + KVBackend KVBackend +} + // Config defines all necessary options for database. type Config struct { - Preimages bool // Flag whether the preimage of node key is recorded - IsVerkle bool // Flag whether the db is holding a verkle tree - HashDB *hashdb.Config // Configs for hash-based scheme - PathDB *pathdb.Config // Configs for experimental path-based scheme + Preimages bool // Flag whether the preimage of node key is recorded + IsVerkle bool // Flag whether the db is holding a verkle tree + HashDB *hashdb.Config // Configs for hash-based scheme + PathDB *pathdb.Config // Configs for experimental path-based scheme + KeyValueDB *KeyValueConfig } // HashDefaults represents a config for using hash-based scheme with @@ -88,6 +116,10 @@ type Database struct { backend backend // The backend for managing trie nodes } +func (db *Database) Config() *Config { + return db.config +} + // NewDatabase initializes the trie database with default settings, note // the legacy hash-based scheme is used by default. func NewDatabase(diskdb ethdb.Database, config *Config) *Database { @@ -155,6 +187,11 @@ func (db *Database) Commit(root common.Hash, report bool) error { if db.preimages != nil { db.preimages.commit(true) } + if db.config.KeyValueDB != nil { + if backend := db.config.KeyValueDB.KVBackend; backend != nil { + return backend.Commit(root) + } + } return db.backend.Commit(root, report) } From 25db320ff87ad5cbaa03508a4b0949fb555e6e1a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 13 Dec 2024 12:38:20 -0800 Subject: [PATCH 025/307] fix non-pruning / stacking views --- plugin/evm/reprocess_test.go | 2 +- shim/merkledb/merkledb.go | 36 +++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c7582e68e5..61ae6105f4 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -71,7 +71,7 @@ func TestReprocessGenesis(t *testing.T) { } cacheConfig.TriePrefetcherParallelism = 4 cacheConfig.SnapshotLimit = 0 - cacheConfig.Pruning = false + // cacheConfig.Pruning = false bc, err := core.NewBlockChain(db, &cacheConfig, g, engine, vm.Config{}, common.Hash{}, false) require.NoError(t, err) diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index 88d4dfcee1..5a65bcb019 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -13,9 +13,9 @@ import ( var _ triedb.KVBackend = &MerkleDB{} type MerkleDB struct { - db merkledb.MerkleDB - pendingView merkledb.View - pendingViewRoot common.Hash + db merkledb.MerkleDB + pendingViews []merkledb.View + pendingViewRoots []common.Hash } func NewMerkleDB(db merkledb.MerkleDB) *MerkleDB { @@ -23,13 +23,20 @@ func NewMerkleDB(db merkledb.MerkleDB) *MerkleDB { } func (m *MerkleDB) Get(key []byte) ([]byte, error) { - val, err := m.db.Get(key) + val, err := m.latestView().GetValue(context.TODO(), key) if err == database.ErrNotFound { return nil, nil } return val, err } +func (m *MerkleDB) latestView() merkledb.Trie { + if len(m.pendingViews) == 0 { + return m.db + } + return m.pendingViews[len(m.pendingViews)-1] +} + func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { ctx := context.TODO() changes := make([]database.BatchOp, len(batch)) @@ -40,7 +47,7 @@ func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { Delete: len(kv.Value) == 0, } } - view, err := m.db.NewView(ctx, merkledb.ViewChanges{BatchOps: changes}) + view, err := m.latestView().NewView(ctx, merkledb.ViewChanges{BatchOps: changes}) if err != nil { return common.Hash{}, err } @@ -48,28 +55,31 @@ func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { if err != nil { return common.Hash{}, err } - m.pendingView = view - m.pendingViewRoot = common.Hash(root) + m.pendingViews = append(m.pendingViews, view) + m.pendingViewRoots = append(m.pendingViewRoots, common.Hash(root)) return common.Hash(root), nil } func (m *MerkleDB) Commit(root common.Hash) error { - if m.pendingViewRoot != root { - return fmt.Errorf("root mismatch: expected %x, got %x", root, m.pendingViewRoot) + if len(m.pendingViews) == 0 { + return fmt.Errorf("no pending views") + } + if m.pendingViewRoots[0] != root { + return fmt.Errorf("root mismatch: expected %x, got %x", root, m.pendingViewRoots[0]) } ctx := context.TODO() - if err := m.pendingView.CommitToDB(ctx); err != nil { + if err := m.pendingViews[0].CommitToDB(ctx); err != nil { return err } - m.pendingView = nil - m.pendingViewRoot = common.Hash{} + m.pendingViews = m.pendingViews[1:] + m.pendingViewRoots = m.pendingViewRoots[1:] fmt.Printf("Commit: %x\n", root) return nil } func (m *MerkleDB) Root() common.Hash { ctx := context.TODO() - root, err := m.db.GetMerkleRoot(ctx) + root, err := m.latestView().GetMerkleRoot(ctx) if err != nil { panic(fmt.Sprintf("failed to get merkle root: %v", err)) } From b016b4b6591761cb73e7aecc6e55abe2eab5eb3c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 13 Dec 2024 13:41:47 -0800 Subject: [PATCH 026/307] making sure it works for storage tries --- plugin/evm/reprocess_test.go | 9 ++++++++- shim/kv_backend.go | 5 +++++ shim/trie.go | 5 ----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 61ae6105f4..9200ebc780 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" + "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" @@ -41,8 +42,14 @@ func TestReprocessGenesis(t *testing.T) { Config: chainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, } + someAddr := common.Address{1} + cbs := dummy.ConsensusCallbacks{ - OnExtraStateChange: testVM.onExtraStateChange, + OnExtraStateChange: func(block *types.Block, statedb *state.StateDB) (*big.Int, *big.Int, error) { + i := byte(block.Number().Uint64()) + statedb.SetState(someAddr, common.Hash{i}, common.Hash{i}) + return testVM.onExtraStateChange(block, statedb) + }, } ctx := context.Background() diff --git a/shim/kv_backend.go b/shim/kv_backend.go index 5aa0947e15..44b1c9e382 100644 --- a/shim/kv_backend.go +++ b/shim/kv_backend.go @@ -29,6 +29,7 @@ type KVTrieBackend struct { } func (k *KVTrieBackend) Get(key []byte) ([]byte, error) { + fmt.Printf("Get: %x\n", key) return k.backend.Get(key) } @@ -36,6 +37,10 @@ func (k *KVTrieBackend) Hash(batch Batch) common.Hash { if k.hashed { return k.hash } + fmt.Printf("Update Total: %d\n", len(batch)) + for _, kv := range batch { + fmt.Printf("Update: %x %x\n", kv.Key, kv.Value) + } root, err := k.backend.Update(batch) if err != nil { panic(fmt.Sprintf("failed to update trie: %v", err)) diff --git a/shim/trie.go b/shim/trie.go index efc0df8fd2..bf3ca34a23 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -39,7 +39,6 @@ func (t *Trie) getKey(key []byte) []byte { // Update batches updates to the trie func (t *Trie) Update(key, value []byte) error { - fmt.Printf("Update: %x -> %x\n", key, value) key = t.getKey(key) value = bytes.Clone(value) if t.parent != nil { @@ -51,7 +50,6 @@ func (t *Trie) Update(key, value []byte) error { } func (t *Trie) Delete(key []byte) error { - fmt.Printf("Delete: %x\n", key) key = t.getKey(key) if t.parent != nil { t.parent.changes = append(t.parent.changes, KV{Key: key}) @@ -66,7 +64,6 @@ func (t *Trie) Hash() common.Hash { return common.Hash{} } - fmt.Printf("Hashing %d changes\n", len(t.changes)) return t.backend.Hash(t.changes) } @@ -75,12 +72,10 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) return common.Hash{}, nil, nil } - fmt.Printf("Committing %d changes\n", len(t.changes)) return t.backend.Commit(t.changes, collectLeaf) } func (t *Trie) Get(key []byte) ([]byte, error) { - fmt.Printf("Get: %x\n", key) key = t.getKey(key) return t.backend.Get(key) } From 5b25442275dc7be0d947a66818e736076a0523c7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 13 Dec 2024 16:23:17 -0800 Subject: [PATCH 027/307] add snapshots --- core/blockchain.go | 30 ++++++++++++++++++++++++++++++ core/genesis.go | 23 +++++++++++++---------- core/state/trie_prefetcher.go | 15 ++++++++++++++- plugin/evm/reprocess_test.go | 25 ++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index ae8dd0faaf..329d147186 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -260,6 +260,7 @@ func (b *blockRoot) Root() common.Hash { return b.root } type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning + genesis *Genesis db ethdb.Database // Low level persistent database to store final content in snaps *snapshot.Tree // Snapshot tree for fast trie leaf access @@ -395,6 +396,7 @@ func NewBlockChain( acceptorQueue: make(chan *blockRoot, cacheConfig.AcceptorQueueLimit), quit: make(chan struct{}), acceptedLogsCache: NewFIFOCache[common.Hash, [][]*types.Log](cacheConfig.AcceptedCacheSize), + genesis: genesis, } bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) bc.validator = NewBlockValidator(chainConfig, bc, engine) @@ -1915,6 +1917,34 @@ func (bc *BlockChain) initSnapshot(b *types.Header) { AsyncBuild: asyncBuild, SkipVerify: !bc.cacheConfig.SnapshotVerify, } + if b.Number.Uint64() == 0 && bc.cacheConfig.KeyValueDB != nil { + snapcfgGenesis := snapconfig + snapcfgGenesis.AsyncBuild = !bc.cacheConfig.SnapshotWait + var err error + bc.snaps, err = snapshot.New(snapcfgGenesis, bc.db, bc.triedb, common.Hash{}, types.EmptyRootHash) + if err != nil { + log.Error("failed to initialize snapshots", "headRoot", b.Root, "err", err, "async", asyncBuild) + } + tmpDiskDB := rawdb.NewMemoryDatabase() + tmpTrieDB := triedb.NewDatabase(tmpDiskDB, nil) + tmpStateDatabase := state.NewDatabaseWithNodeDB(tmpDiskDB, tmpTrieDB) + statedb, err := state.New(types.EmptyRootHash, tmpStateDatabase, bc.snaps) + if err != nil { + log.Error("failed to initialize genesis state", "err", err) + } + bc.genesis.toBlockWithState(tmpDiskDB, statedb) + _, err = statedb.CommitWithSnap(0, false, bc.snaps, bc.genesisBlock.Hash(), common.Hash{}) + if err != nil { + log.Error("failed to commit genesis state", "err", err) + } + if err := bc.snaps.Flatten(bc.genesisBlock.Hash()); err != nil { + log.Error("failed to flatten genesis snapshot", "err", err) + } + rawdb.WriteSnapshotRoot(bc.db, bc.genesisBlock.Root()) + // Need to mark the snapshot completed + bc.snaps.AbortGeneration() + snapshot.ResetSnapshotGeneration(bc.db) + } var err error bc.snaps, err = snapshot.New(snapconfig, bc.db, bc.triedb, b.Hash(), b.Root) if err != nil { diff --git a/core/genesis.go b/core/genesis.go index 07d3ba072d..c889ce6920 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -230,7 +230,18 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo if err != nil { panic(err) } + head, root := g.toBlockWithState(db, statedb) + statedb.Commit(0, false) + // Commit newly generated states into disk if it's not empty. + if root != types.EmptyRootHash { + if err := triedb.Commit(root, true); err != nil { + panic(fmt.Sprintf("unable to commit genesis block: %v", err)) + } + } + return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) +} +func (g *Genesis) toBlockWithState(db ethdb.Database, statedb *state.StateDB) (*types.Header, common.Hash) { head := &types.Header{ Number: new(big.Int).SetUint64(g.Number), Nonce: types.EncodeNonce(g.Nonce), @@ -246,7 +257,7 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo } // Configure any stateful precompiles that should be enabled in the genesis. - err = ApplyPrecompileActivations(g.Config, nil, types.NewBlockWithHeader(head), statedb) + err := ApplyPrecompileActivations(g.Config, nil, types.NewBlockWithHeader(head), statedb) if err != nil { panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err)) } @@ -298,15 +309,7 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo } } } - - statedb.Commit(0, false) - // Commit newly generated states into disk if it's not empty. - if root != types.EmptyRootHash { - if err := triedb.Commit(root, true); err != nil { - panic(fmt.Sprintf("unable to commit genesis block: %v", err)) - } - } - return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) + return head, root } // Commit writes the block and state of a genesis specification to the database. diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 5b01083f59..65c7f37231 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -46,6 +46,7 @@ const triePrefetchMetricsPrefix = "trie/prefetch/" // Note, the prefetcher's API is not thread safe. type triePrefetcher struct { db Database // Database to fetch trie nodes through + rootTrie Trie // Root trie for the state trie root common.Hash // Root hash of the account trie for metrics fetches map[string]Trie // Partially or fully fetched tries. Only populated for inactive copies. fetchers map[string]*subfetcher // Subfetchers for each trie @@ -221,6 +222,18 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm fetcher.schedule(keys) } +func (p *triePrefetcher) getRootTrie() Trie { + if p.rootTrie == nil { + var err error + p.rootTrie, err = p.db.OpenTrie(p.root) + if err != nil { + log.Warn("Trie prefetcher failed opening root trie", "root", p.root, "err", err) + return nil + } + } + return p.rootTrie +} + // trie returns the trie matching the root hash, or nil if the prefetcher doesn't // have it. func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { @@ -429,7 +442,7 @@ func newTrieOrchestrator(sf *subfetcher) *trieOrchestrator { } else { // The trie argument can be nil as verkle doesn't support prefetching // yet. TODO FIX IT(rjl493456442), otherwise code will panic here. - base, err = sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil) + base, err = sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, sf.p.getRootTrie()) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) return nil diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 9200ebc780..230889b410 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -47,6 +47,7 @@ func TestReprocessGenesis(t *testing.T) { cbs := dummy.ConsensusCallbacks{ OnExtraStateChange: func(block *types.Block, statedb *state.StateDB) (*big.Int, *big.Int, error) { i := byte(block.Number().Uint64()) + statedb.SetNonce(someAddr, uint64(i)) statedb.SetState(someAddr, common.Hash{i}, common.Hash{i}) return testVM.onExtraStateChange(block, statedb) }, @@ -77,7 +78,8 @@ func TestReprocessGenesis(t *testing.T) { KVBackend: merkledb.NewMerkleDB(mdb), } cacheConfig.TriePrefetcherParallelism = 4 - cacheConfig.SnapshotLimit = 0 + cacheConfig.SnapshotLimit = 256 + cacheConfig.SnapshotDelayInit = true // cacheConfig.Pruning = false bc, err := core.NewBlockChain(db, &cacheConfig, g, engine, vm.Config{}, common.Hash{}, false) @@ -95,6 +97,8 @@ func TestReprocessGenesis(t *testing.T) { // bc.WriteHeadBlock(normalGenesis) require.NoError(t, bc.LoadGenesisState(normalGenesis)) + bc.InitializeSnapshots() + t.Logf("Genesis block: %s", bc.CurrentBlock().Hash().Hex()) getCurrentRoot := func() common.Hash { return cacheConfig.KeyValueDB.KVBackend.Root() @@ -128,6 +132,7 @@ func TestReprocessGenesis(t *testing.T) { // Restore parent root parent.Root = originalParentRoot + t.Logf("Accepting block %s", block.Hash().Hex()) err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) @@ -135,4 +140,22 @@ func TestReprocessGenesis(t *testing.T) { lastRoot = getCurrentRoot() } + + it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) + defer it.Release() + for it.Next() { + if len(it.Key()) != 33 { + continue + } + t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) + } + + it2 := db.NewIterator(rawdb.SnapshotStoragePrefix, nil) + defer it2.Release() + for it2.Next() { + // if len(it2.Key()) != 65 { + // continue + // } + t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) + } } From e722e8f5de3f4f600ea054d092550abcb7977a34 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 13 Dec 2024 16:24:45 -0800 Subject: [PATCH 028/307] nit --- plugin/evm/reprocess_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 230889b410..061723216a 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -153,9 +153,9 @@ func TestReprocessGenesis(t *testing.T) { it2 := db.NewIterator(rawdb.SnapshotStoragePrefix, nil) defer it2.Release() for it2.Next() { - // if len(it2.Key()) != 65 { - // continue - // } + if len(it2.Key()) != 65 { + continue + } t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) } } From 6d6fcc9cb9a62c19f81414f2b9347ff028499895 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 13 Dec 2024 16:36:50 -0800 Subject: [PATCH 029/307] clarify interface --- triedb/database.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/triedb/database.go b/triedb/database.go index 07f7132c12..a8c1502603 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -43,13 +43,16 @@ type KVBackend interface { Root() common.Hash // Get retrieves the value for the given key. + // If the key does not exist, it must return (nil, nil). Get(key []byte) ([]byte, error) // After this call, Root() should return the same hash as returned by this call. + // Note when len(Value) == 0, it means the key should be deleted. Update(Batch) (common.Hash, error) // After this call, changes related to [root] should be persisted to disk. - // This may be implemented as no-op if Update already persists changes. + // This may be implemented as no-op if Update already persists changes, or + // commits happen on a rolling basis. Commit(root common.Hash) error } From b963cf9853dda34387ef9d5e5640130a94f05f33 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 16 Dec 2024 18:45:08 -0800 Subject: [PATCH 030/307] fixes, support for start/stop --- core/blockchain.go | 45 +++++++++++++++------ core/state/statedb.go | 1 + core/state/trie_prefetcher.go | 6 +-- plugin/evm/reprocess_test.go | 74 ++++++++++++++++++++++++++++++++--- shim/merkledb/merkledb.go | 18 +++++++++ shim/trie.go | 6 +-- triedb/database.go | 5 +++ 7 files changed, 129 insertions(+), 26 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 329d147186..6d2ccbb483 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -349,12 +349,17 @@ type BlockChain struct { txIndexTailLock sync.Mutex } +type Opts struct { + LastAcceptedRoot common.Hash +} + // NewBlockChain returns a fully initialised block chain using information // available in the database. It initialises the default Ethereum Validator and // Processor. func NewBlockChain( db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, engine consensus.Engine, vmConfig vm.Config, lastAcceptedHash common.Hash, skipChainConfigCheckCompatible bool, + opts ...Opts, ) (*BlockChain, error) { if cacheConfig == nil { return nil, errCacheConfigNotSpecified @@ -421,7 +426,11 @@ func NewBlockChain( // } // Re-generate current block state if it is missing - if err := bc.loadLastState(lastAcceptedHash); err != nil { + lastAcceptedRoot := common.Hash{} + if len(opts) > 0 { + lastAcceptedRoot = opts[0].LastAcceptedRoot + } + if err := bc.loadLastState(lastAcceptedHash, lastAcceptedRoot); err != nil { return nil, err } @@ -434,7 +443,11 @@ func NewBlockChain( // Make sure the state associated with the block is available head := bc.CurrentBlock() - if !bc.HasState(head.Root) { + headRoot := head.Root + if len(opts) > 0 { + headRoot = opts[0].LastAcceptedRoot + } + if !bc.HasState(headRoot) { return nil, fmt.Errorf("head state missing %d:%s", head.Number, head.Hash()) } @@ -672,12 +685,12 @@ func (bc *BlockChain) stopAcceptor() { close(bc.acceptorQueue) } -func (bc *BlockChain) InitializeSnapshots() { +func (bc *BlockChain) InitializeSnapshots(opts ...*Opts) { bc.chainmu.Lock() defer bc.chainmu.Unlock() head := bc.CurrentBlock() - bc.initSnapshot(head) + bc.initSnapshot(head, opts...) } // SenderCacher returns the *TxSenderCacher used within the core package. @@ -687,7 +700,7 @@ func (bc *BlockChain) SenderCacher() *TxSenderCacher { // loadLastState loads the last known chain state from the database. This method // assumes that the chain manager mutex is held. -func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error { +func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash, lastAcceptedRoot common.Hash) error { // Initialize genesis state if lastAcceptedHash == (common.Hash{}) { return bc.loadGenesisState() @@ -732,7 +745,7 @@ func (bc *BlockChain) loadLastState(lastAcceptedHash common.Hash) error { // reprocessState is necessary to ensure that the last accepted state is // available. The state may not be available if it was not committed due // to an unclean shutdown. - return bc.reprocessState(bc.lastAccepted, 2*bc.cacheConfig.CommitInterval) + return bc.reprocessState(bc.lastAccepted, lastAcceptedRoot, 2*bc.cacheConfig.CommitInterval) } func (bc *BlockChain) LoadGenesisState(block *types.Block) error { @@ -1897,7 +1910,7 @@ func (bc *BlockChain) reprocessFromGenesis() error { } // initSnapshot instantiates a Snapshot instance and adds it to [bc] -func (bc *BlockChain) initSnapshot(b *types.Header) { +func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { if bc.cacheConfig.SnapshotLimit <= 0 || bc.snaps != nil { return } @@ -1946,9 +1959,13 @@ func (bc *BlockChain) initSnapshot(b *types.Header) { snapshot.ResetSnapshotGeneration(bc.db) } var err error - bc.snaps, err = snapshot.New(snapconfig, bc.db, bc.triedb, b.Hash(), b.Root) + root := b.Root + if len(opts) > 0 { + root = opts[0].LastAcceptedRoot + } + bc.snaps, err = snapshot.New(snapconfig, bc.db, bc.triedb, b.Hash(), root) if err != nil { - log.Error("failed to initialize snapshots", "headHash", b.Hash(), "headRoot", b.Root, "err", err, "async", asyncBuild) + log.Error("failed to initialize snapshots", "headHash", b.Hash(), "headRoot", root, "err", err, "async", asyncBuild) } } @@ -1956,7 +1973,7 @@ func (bc *BlockChain) initSnapshot(b *types.Header) { // it reaches a block with a state committed to the database. reprocessState does not use // snapshots since the disk layer for snapshots will most likely be above the last committed // state that reprocessing will start from. -func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error { +func (bc *BlockChain) reprocessState(current *types.Block, currentRoot common.Hash, reexec uint64) error { origin := current.NumberU64() acceptorTip, err := rawdb.ReadAcceptorTip(bc.db) if err != nil { @@ -1969,7 +1986,11 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error acceptorTipUpToDate := acceptorTip == (common.Hash{}) || acceptorTip == current.Hash() // If the state is already available and the acceptor tip is up to date, skip re-processing. - if bc.HasState(current.Root()) && acceptorTipUpToDate { + root := current.Root() + if currentRoot != (common.Hash{}) { + root = currentRoot + } + if bc.HasState(root) && acceptorTipUpToDate { log.Info("Skipping state reprocessing", "root", current.Root()) return nil } @@ -2298,7 +2319,7 @@ func (bc *BlockChain) ResetToStateSyncedBlock(block *types.Block) error { lastAcceptedHash := block.Hash() bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) - if err := bc.loadLastState(lastAcceptedHash); err != nil { + if err := bc.loadLastState(lastAcceptedHash, common.Hash{}); err != nil { return err } // Create the state manager diff --git a/core/state/statedb.go b/core/state/statedb.go index dce72992d3..1454589434 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -214,6 +214,7 @@ func (s *StateDB) StartPrefetcher(namespace string, maxConcurrency int) { } if s.snap != nil { s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) + s.prefetcher.rootTrie = s.trie } } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 65c7f37231..c2c4766281 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -434,11 +434,7 @@ func newTrieOrchestrator(sf *subfetcher) *trieOrchestrator { err error ) if sf.owner == (common.Hash{}) { - base, err = sf.db.OpenTrie(sf.root) - if err != nil { - log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) - return nil - } + base = sf.p.getRootTrie() } else { // The trie argument can be nil as verkle doesn't support prefetching // yet. TODO FIX IT(rjl493456442), otherwise code will panic here. diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 061723216a..29c273d136 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -2,6 +2,7 @@ package evm import ( "context" + "encoding/binary" "math/big" "testing" @@ -46,9 +47,11 @@ func TestReprocessGenesis(t *testing.T) { cbs := dummy.ConsensusCallbacks{ OnExtraStateChange: func(block *types.Block, statedb *state.StateDB) (*big.Int, *big.Int, error) { - i := byte(block.Number().Uint64()) - statedb.SetNonce(someAddr, uint64(i)) - statedb.SetState(someAddr, common.Hash{i}, common.Hash{i}) + i := block.Number().Uint64() + statedb.SetNonce(someAddr, i) + iBytes := binary.BigEndian.AppendUint64(nil, i) + asHash := common.BytesToHash(iBytes) + statedb.SetState(someAddr, asHash, asHash) return testVM.onExtraStateChange(block, statedb) }, } @@ -86,11 +89,12 @@ func TestReprocessGenesis(t *testing.T) { require.NoError(t, err) var lastInsertedRoot common.Hash - bc.Validator().(*core.BlockValidator).CheckRoot = func(expected, got common.Hash) bool { + checkRootFn := func(expected, got common.Hash) bool { t.Logf("Got root: %s", got.Hex()) lastInsertedRoot = got return true } + bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn normalGenesis := g.ToBlock() // rawdb.WriteHeader(db, normalGenesis.Header()) @@ -107,7 +111,7 @@ func TestReprocessGenesis(t *testing.T) { // Let's generate some blocks signer := types.LatestSigner(chainConfig) - _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, 10, 2, func(i int, b *core.BlockGen) { + _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, 20, 2, func(i int, b *core.BlockGen) { tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ Nonce: uint64(i), GasPrice: b.BaseFee(), @@ -116,6 +120,10 @@ func TestReprocessGenesis(t *testing.T) { }), signer, key1) b.AddTx(tx) }) + insertAfterRestart := blocks[10:] + insertNow := blocks[:10] + blocks = insertNow + require.NoError(t, err) t.Logf("Generated %d blocks", len(blocks)) for _, block := range blocks { @@ -141,6 +149,9 @@ func TestReprocessGenesis(t *testing.T) { lastRoot = getCurrentRoot() } + // Great, now let's try to stop and restart the chain + bc.Stop() + it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) defer it.Release() for it.Next() { @@ -158,4 +169,57 @@ func TestReprocessGenesis(t *testing.T) { } t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) } + + lastAccepted := blocks[len(blocks)-1] + cacheConfig.SnapshotNoBuild = true + bc, err = core.NewBlockChain( + db, &cacheConfig, g, engine, vm.Config{}, lastAccepted.Hash(), false, + core.Opts{LastAcceptedRoot: lastRoot}, + ) + require.NoError(t, err) + bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn + bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) + + blocks = insertAfterRestart + for _, block := range blocks { + t.Logf("Transactions: %d, Parent State: %x", len(block.Transactions()), lastRoot) + + // Override parentRoot to match last state + parent := bc.GetHeaderByNumber(block.NumberU64() - 1) + originalParentRoot := parent.Root + parent.Root = lastRoot + + err := bc.InsertBlockManualWithParent(block, parent, true) + require.NoError(t, err) + + // Restore parent root + parent.Root = originalParentRoot + + t.Logf("Accepting block %s", block.Hash().Hex()) + err = bc.AcceptWithRoot(block, lastInsertedRoot) + require.NoError(t, err) + + bc.DrainAcceptorQueue() + + lastRoot = getCurrentRoot() + } + bc.Stop() + + it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil) + defer it.Release() + for it.Next() { + if len(it.Key()) != 33 { + continue + } + t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) + } + + it2 = db.NewIterator(rawdb.SnapshotStoragePrefix, nil) + defer it2.Release() + for it2.Next() { + if len(it2.Key()) != 65 { + continue + } + t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) + } } diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index 5a65bcb019..c9d6483ba5 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -64,6 +64,24 @@ func (m *MerkleDB) Commit(root common.Hash) error { if len(m.pendingViews) == 0 { return fmt.Errorf("no pending views") } + pendingRootIdx := -1 + for i, pendingRoot := range m.pendingViewRoots { + if pendingRoot == root { + pendingRootIdx = i + break + } + } + if pendingRootIdx > 0 { + for i := 0; i < pendingRootIdx; i++ { + if err := m.commitToDisk(m.pendingViewRoots[0]); err != nil { + return err + } + } + } + return m.commitToDisk(root) +} + +func (m *MerkleDB) commitToDisk(root common.Hash) error { if m.pendingViewRoots[0] != root { return fmt.Errorf("root mismatch: expected %x, got %x", root, m.pendingViewRoots[0]) } diff --git a/shim/trie.go b/shim/trie.go index bf3ca34a23..5fe9315a1c 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -81,10 +81,8 @@ func (t *Trie) Get(key []byte) ([]byte, error) { } func (t *Trie) Copy() *Trie { - fmt.Printf("Copy requested (%d changes): %p\n", len(t.changes), t) - changes := make(Batch, len(t.changes)) - copy(changes, t.changes) - return &Trie{backend: t.backend, changes: changes} + fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) + return t } func (t *Trie) NodeIterator(start []byte) (trie.NodeIterator, error) { panic("not implemented") } diff --git a/triedb/database.go b/triedb/database.go index a8c1502603..49303a7bea 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -216,6 +216,11 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize, common.Stora // Initialized returns an indicator if the state data is already initialized // according to the state scheme. func (db *Database) Initialized(genesisRoot common.Hash) bool { + if db.config.KeyValueDB != nil { + if backend := db.config.KeyValueDB.KVBackend; backend != nil { + return backend.Root() != common.Hash{} + } + } return db.backend.Initialized(genesisRoot) } From 17ca744992aba2759f153a9521b409208d22f6a9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 16 Dec 2024 18:56:53 -0800 Subject: [PATCH 031/307] "fix" races --- shim/kv_backend.go | 6 +++--- shim/secure_trie.go | 4 ++-- shim/trie.go | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/shim/kv_backend.go b/shim/kv_backend.go index 44b1c9e382..82bb1ee3a1 100644 --- a/shim/kv_backend.go +++ b/shim/kv_backend.go @@ -68,7 +68,7 @@ func NewAccountTrieKV(stateRoot common.Hash, kv KVBackend, db database.Database) return nil, fmt.Errorf("%w: expected %x, got %x", ErrRootMismatch, stateRoot, kvRoot) } - tr := Trie{ + tr := &Trie{ backend: &KVTrieBackend{backend: kv}, origin: kvRoot, } @@ -83,8 +83,8 @@ func NewStorageTrieKV(stateRoot common.Hash, account common.Hash, accountTrie *S if accountTrie.trie.origin != stateRoot { return nil, fmt.Errorf("%w: expected %x, got %x", ErrStorageStateRootMismatch, stateRoot, accountTrie.trie.origin) } - tr := Trie{ - parent: &accountTrie.trie, + tr := &Trie{ + parent: accountTrie.trie, backend: accountTrie.trie.backend, prefix: account.Bytes(), } diff --git a/shim/secure_trie.go b/shim/secure_trie.go index 25f9810aa8..571be9564f 100644 --- a/shim/secure_trie.go +++ b/shim/secure_trie.go @@ -47,7 +47,7 @@ import ( // // StateTrie is not safe for concurrent use. type StateTrie struct { - trie Trie + trie *Trie db database.Database hashKeyBuf [common.HashLength]byte secKeyCache map[string][]byte @@ -185,7 +185,7 @@ func (t *StateTrie) Hash() common.Hash { // Copy returns a copy of StateTrie. func (t *StateTrie) Copy() *StateTrie { return &StateTrie{ - trie: *t.trie.Copy(), + trie: t.trie.Copy(), db: t.db, secKeyCache: t.secKeyCache, } diff --git a/shim/trie.go b/shim/trie.go index 5fe9315a1c..62b4d227cb 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -2,7 +2,6 @@ package shim import ( "bytes" - "fmt" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -28,7 +27,7 @@ type Backend interface { func NewStateTrie(backend Backend, db database.Database) *StateTrie { return &StateTrie{ - trie: Trie{backend: backend}, + trie: &Trie{backend: backend}, db: db, } } @@ -81,7 +80,7 @@ func (t *Trie) Get(key []byte) ([]byte, error) { } func (t *Trie) Copy() *Trie { - fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) + // fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) return t } From 02d577e65b2629e8ce106b85e16b329ccfd7724f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 13:29:12 -0800 Subject: [PATCH 032/307] try export blocks --- plugin/evm/reprocess_test.go | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 29c273d136..33fd2dc789 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -1,12 +1,16 @@ package evm import ( + "bytes" "context" "encoding/binary" + "encoding/hex" + "flag" "math/big" "testing" "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/trace" @@ -23,10 +27,100 @@ import ( "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) +var ( + sourceDbDir = "sourceDb" + sourcePrefix = "" + dbDir = "db" + startBlock = uint64(0) + endBlock = uint64(20_000) +) + +func TestMain(m *testing.M) { + flag.StringVar(&sourceDbDir, "sourceDbDir", sourceDbDir, "directory of source database") + flag.StringVar(&sourcePrefix, "sourcePrefix", sourcePrefix, "prefix of source database") + flag.StringVar(&dbDir, "dbDir", dbDir, "directory to store database") + flag.Uint64Var(&startBlock, "startBlock", startBlock, "start block number") + flag.Uint64Var(&endBlock, "endBlock", endBlock, "end block number") +} + +type prefixReader struct { + ethdb.Database + prefix []byte +} + +func (r *prefixReader) Get(key []byte) ([]byte, error) { + return r.Database.Get(append(r.prefix, key...)) +} + +func (r *prefixReader) Has(key []byte) (bool, error) { + return r.Database.Has(append(r.prefix, key...)) +} + +func TestExportBlocks(t *testing.T) { + cache := 128 + handles := 1024 + + sourceDb, err := rawdb.NewLevelDBDatabase(sourceDbDir, cache, handles, "", true) + require.NoError(t, err) + defer sourceDb.Close() + + prefix := []byte(sourcePrefix) + if bytes.HasPrefix(prefix, []byte("0x")) { + prefix = prefix[2:] + var err error + prefix, err = hex.DecodeString(string(prefix)) + if err != nil { + t.Fatalf("invalid hex prefix: %s", prefix) + } + } + t.Logf("Using prefix: %x", prefix) + sourceDb = &prefixReader{Database: sourceDb, prefix: prefix} + + if startBlock == 0 { + startBlock = 1 + t.Logf("Start block is 0, setting to 1") + } + + db, err := rawdb.NewLevelDBDatabase(dbDir, cache, handles, "", false) + require.NoError(t, err) + defer db.Close() + + logEach := 1_000 + for i := startBlock; i <= endBlock; i++ { + hash := rawdb.ReadCanonicalHash(sourceDb, i) + block := rawdb.ReadBlock(sourceDb, hash, i) + if block == nil { + t.Fatalf("Block %d not found", i) + } + rawdb.WriteCanonicalHash(db, hash, i) + rawdb.WriteBlock(db, block) + if i%uint64(logEach) == 0 { + t.Logf("Exported block %d", i) + } + } +} + +var ( + fujiXChainID = ids.FromStringOrPanic("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm") + fujiCChainID = ids.FromStringOrPanic("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp") + mainnetXChainID = ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM") + mainnetCChainID = ids.FromStringOrPanic("2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5") + VMDBPrefix = []byte("vm") +) + +func TestCalculatePrefix(t *testing.T) { + prefix := prefixdb.JoinPrefixes( + prefixdb.MakePrefix(mainnetCChainID[:]), + VMDBPrefix, + ) + t.Logf("Prefix: %x", prefix) +} + func TestReprocessGenesis(t *testing.T) { chainConfig := params.TestChainConfig testVM := &VM{ From 2e44670da9abbf4c911a3a62141cfe9db5f820a3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 13:36:05 -0800 Subject: [PATCH 033/307] fix --- plugin/evm/reprocess_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 33fd2dc789..48685a91b4 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -46,6 +46,9 @@ func TestMain(m *testing.M) { flag.StringVar(&dbDir, "dbDir", dbDir, "directory to store database") flag.Uint64Var(&startBlock, "startBlock", startBlock, "start block number") flag.Uint64Var(&endBlock, "endBlock", endBlock, "end block number") + + flag.Parse() + m.Run() } type prefixReader struct { From 8379845e298047a00c5d93120e667c3e0197f6e5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 13:44:21 -0800 Subject: [PATCH 034/307] minor updates --- plugin/evm/reprocess_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 48685a91b4..5c3c1a33a4 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -93,7 +93,7 @@ func TestExportBlocks(t *testing.T) { require.NoError(t, err) defer db.Close() - logEach := 1_000 + logEach := 100_000 for i := startBlock; i <= endBlock; i++ { hash := rawdb.ReadCanonicalHash(sourceDb, i) block := rawdb.ReadBlock(sourceDb, hash, i) @@ -106,6 +106,8 @@ func TestExportBlocks(t *testing.T) { t.Logf("Exported block %d", i) } } + + t.Logf("Exported %d blocks", endBlock-startBlock+1) } var ( @@ -121,6 +123,8 @@ func TestCalculatePrefix(t *testing.T) { prefixdb.MakePrefix(mainnetCChainID[:]), VMDBPrefix, ) + + prefix = append(prefix, prefixdb.MakePrefix(ethDBPrefix)...) t.Logf("Prefix: %x", prefix) } From 3db00608543bbb00a5cce22ff89c086530181ace Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 14:17:28 -0800 Subject: [PATCH 035/307] log avax assetID --- plugin/evm/vm.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 5cfbb6b5a0..c7485cae05 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -410,6 +410,7 @@ func (vm *VM) Initialize( } vm.logger = corethLogger + log.Info("AVAX assetID", "assetID", vm.ctx.AVAXAssetID) log.Info("Initializing Coreth VM", "Version", Version, "Config", vm.config) if deprecateMsg != "" { From 97eb4c4fa80109c8be98a8209230f182d12d9969 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 14:17:43 -0800 Subject: [PATCH 036/307] refactor towards pluggable backend --- plugin/evm/reprocess_backend_test.go | 78 ++++++++++++++++++++++++++ plugin/evm/reprocess_test.go | 83 +++++++--------------------- 2 files changed, 98 insertions(+), 63 deletions(-) create mode 100644 plugin/evm/reprocess_backend_test.go diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go new file mode 100644 index 0000000000..95caa80c1a --- /dev/null +++ b/plugin/evm/reprocess_backend_test.go @@ -0,0 +1,78 @@ +package evm + +import ( + "encoding/binary" + "math/big" + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/coreth/consensus" + "github.com/ava-labs/coreth/consensus/dummy" + "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/state" + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +type reprocessBackend struct { + Genesis *core.Genesis + Engine consensus.Engine + GetBlock func(uint64) *types.Block + BlockCount uint64 +} + +func getBackend(t *testing.T, name string) *reprocessBackend { + chainConfig := params.TestChainConfig + key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 := crypto.PubkeyToAddress(key1.PublicKey) + g := &core.Genesis{ + Config: chainConfig, + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, + } + testVM := &VM{ + chainConfig: chainConfig, + codec: Codec, + ctx: &snow.Context{ + AVAXAssetID: ids.ID{1}, + }, + } + someAddr := common.Address{1} + + cbs := dummy.ConsensusCallbacks{ + OnExtraStateChange: func(block *types.Block, statedb *state.StateDB) (*big.Int, *big.Int, error) { + i := block.Number().Uint64() + statedb.SetNonce(someAddr, i) + iBytes := binary.BigEndian.AppendUint64(nil, i) + asHash := common.BytesToHash(iBytes) + statedb.SetState(someAddr, asHash, asHash) + return testVM.onExtraStateChange(block, statedb) + }, + } + + engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ + ModeSkipHeader: true, + }) + + signer := types.LatestSigner(chainConfig) + _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, 20, 2, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + GasPrice: b.BaseFee(), + Gas: 21000, + To: &addr1, + }), signer, key1) + b.AddTx(tx) + }) + require.NoError(t, err) + + return &reprocessBackend{ + Genesis: g, + Engine: engine, + BlockCount: uint64(len(blocks)), + GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, + } +} diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 5c3c1a33a4..cec7ededb9 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -3,35 +3,32 @@ package evm import ( "bytes" "context" - "encoding/binary" "encoding/hex" "flag" - "math/big" "testing" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/units" xmerkledb "github.com/ava-labs/avalanchego/x/merkledb" - "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/shim/merkledb" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) +var ( + cChainGenesisFuji = "{\"config\":{\"chainId\":43113,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + cChainGenesisMainnet = "{\"config\":{\"chainId\":43114,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" +) + var ( sourceDbDir = "sourceDb" sourcePrefix = "" @@ -129,33 +126,11 @@ func TestCalculatePrefix(t *testing.T) { } func TestReprocessGenesis(t *testing.T) { - chainConfig := params.TestChainConfig - testVM := &VM{ - chainConfig: chainConfig, - codec: Codec, - ctx: &snow.Context{ - AVAXAssetID: ids.ID{1}, - }, - } - key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - addr1 := crypto.PubkeyToAddress(key1.PublicKey) + backend := getBackend(t, "test") + g := backend.Genesis + engine := backend.Engine + db := rawdb.NewMemoryDatabase() - g := &core.Genesis{ - Config: chainConfig, - Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, - } - someAddr := common.Address{1} - - cbs := dummy.ConsensusCallbacks{ - OnExtraStateChange: func(block *types.Block, statedb *state.StateDB) (*big.Int, *big.Int, error) { - i := block.Number().Uint64() - statedb.SetNonce(someAddr, i) - iBytes := binary.BigEndian.AppendUint64(nil, i) - asHash := common.BytesToHash(iBytes) - statedb.SetState(someAddr, asHash, asHash) - return testVM.onExtraStateChange(block, statedb) - }, - } ctx := context.Background() mdbKVStore := memdb.New() @@ -174,9 +149,6 @@ func TestReprocessGenesis(t *testing.T) { }) require.NoError(t, err) - engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ - ModeSkipHeader: true, - }) cacheConfig := *core.DefaultCacheConfig cacheConfig.KeyValueDB = &triedb.KeyValueConfig{ KVBackend: merkledb.NewMerkleDB(mdb), @@ -198,8 +170,6 @@ func TestReprocessGenesis(t *testing.T) { bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn normalGenesis := g.ToBlock() - // rawdb.WriteHeader(db, normalGenesis.Header()) - // bc.WriteHeadBlock(normalGenesis) require.NoError(t, bc.LoadGenesisState(normalGenesis)) bc.InitializeSnapshots() @@ -209,26 +179,12 @@ func TestReprocessGenesis(t *testing.T) { return cacheConfig.KeyValueDB.KVBackend.Root() } lastRoot := getCurrentRoot() + lastHash := normalGenesis.Hash() - // Let's generate some blocks - signer := types.LatestSigner(chainConfig) - _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, 20, 2, func(i int, b *core.BlockGen) { - tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ - Nonce: uint64(i), - GasPrice: b.BaseFee(), - Gas: 21000, - To: &addr1, - }), signer, key1) - b.AddTx(tx) - }) - insertAfterRestart := blocks[10:] - insertNow := blocks[:10] - blocks = insertNow - - require.NoError(t, err) - t.Logf("Generated %d blocks", len(blocks)) - for _, block := range blocks { - t.Logf("Transactions: %d, Parent State: %x", len(block.Transactions()), lastRoot) + start, stop := uint64(1), backend.BlockCount/2 + for i := start; i <= stop; i++ { + block := backend.GetBlock(i) + t.Logf("Block: %d, Transactions: %d, Parent State: %x", i, len(block.Transactions()), lastRoot) // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) @@ -248,6 +204,7 @@ func TestReprocessGenesis(t *testing.T) { bc.DrainAcceptorQueue() lastRoot = getCurrentRoot() + lastHash = block.Hash() } // Great, now let's try to stop and restart the chain @@ -271,19 +228,19 @@ func TestReprocessGenesis(t *testing.T) { t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) } - lastAccepted := blocks[len(blocks)-1] cacheConfig.SnapshotNoBuild = true bc, err = core.NewBlockChain( - db, &cacheConfig, g, engine, vm.Config{}, lastAccepted.Hash(), false, + db, &cacheConfig, g, engine, vm.Config{}, lastHash, false, core.Opts{LastAcceptedRoot: lastRoot}, ) require.NoError(t, err) bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) - blocks = insertAfterRestart - for _, block := range blocks { - t.Logf("Transactions: %d, Parent State: %x", len(block.Transactions()), lastRoot) + start, stop = backend.BlockCount/2+1, backend.BlockCount + for i := start; i <= stop; i++ { + block := backend.GetBlock(i) + t.Logf("Block: %d, Transactions: %d, Parent State: %x", i, len(block.Transactions()), lastRoot) // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) From d500ff978ede06f4b3020016fe84f331aa707b52 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 14:25:16 -0800 Subject: [PATCH 037/307] refactor: separate snapshot verification --- plugin/evm/reprocess_test.go | 51 ++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index cec7ededb9..6b79842bf1 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -206,28 +206,12 @@ func TestReprocessGenesis(t *testing.T) { lastRoot = getCurrentRoot() lastHash = block.Hash() } - - // Great, now let's try to stop and restart the chain bc.Stop() - it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) - defer it.Release() - for it.Next() { - if len(it.Key()) != 33 { - continue - } - t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) - } - - it2 := db.NewIterator(rawdb.SnapshotStoragePrefix, nil) - defer it2.Release() - for it2.Next() { - if len(it2.Key()) != 65 { - continue - } - t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) - } + expectedAccounts, expectedStorages := 3, int(stop) // test backend inserts 1 storage per block + checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) + // Great, now let's restart the chain cacheConfig.SnapshotNoBuild = true bc, err = core.NewBlockChain( db, &cacheConfig, g, engine, vm.Config{}, lastHash, false, @@ -263,21 +247,42 @@ func TestReprocessGenesis(t *testing.T) { } bc.Stop() - it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil) + expectedAccounts, expectedStorages = 3, int(stop) // test backend inserts 1 storage per block + checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) +} + +func checkSnapshot(t *testing.T, db ethdb.Database, expectedAccounts, expectedStorages *int, log bool) { + t.Helper() + + it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) defer it.Release() + accounts := 0 for it.Next() { if len(it.Key()) != 33 { continue } - t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) + accounts++ + if log { + t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) + } + } + if expectedAccounts != nil { + require.Equal(t, *expectedAccounts, accounts) } - it2 = db.NewIterator(rawdb.SnapshotStoragePrefix, nil) + it2 := db.NewIterator(rawdb.SnapshotStoragePrefix, nil) defer it2.Release() + storages := 0 for it2.Next() { if len(it2.Key()) != 65 { continue } - t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) + storages++ + if log { + t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) + } + } + if expectedStorages != nil { + require.Equal(t, *expectedStorages, storages) } } From fe09f8be66325b2bf32d0cfa7a91de633b79c13d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 14:26:40 -0800 Subject: [PATCH 038/307] not needed to drain acceptor queue --- plugin/evm/reprocess_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 6b79842bf1..35cc862063 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -201,8 +201,6 @@ func TestReprocessGenesis(t *testing.T) { err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) - bc.DrainAcceptorQueue() - lastRoot = getCurrentRoot() lastHash = block.Hash() } @@ -241,8 +239,6 @@ func TestReprocessGenesis(t *testing.T) { err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) - bc.DrainAcceptorQueue() - lastRoot = getCurrentRoot() } bc.Stop() From 28e19fecd9b10a5ba0f4c2ce93ffbee86ea20901 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 15:23:17 -0800 Subject: [PATCH 039/307] make it work with LegacyBackend again --- core/blockchain.go | 2 +- core/state/statedb.go | 4 +++- core/state/trie_prefetcher.go | 16 ++++++++-------- plugin/evm/reprocess_backend_test.go | 18 +++++++++++++----- plugin/evm/reprocess_test.go | 19 +++++++++++++------ shim/trie.go | 5 +++++ 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 6d2ccbb483..3297614cb3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1930,7 +1930,7 @@ func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { AsyncBuild: asyncBuild, SkipVerify: !bc.cacheConfig.SnapshotVerify, } - if b.Number.Uint64() == 0 && bc.cacheConfig.KeyValueDB != nil { + if b.Number.Uint64() == 0 && bc.cacheConfig.KeyValueDB.KVBackend != nil { snapcfgGenesis := snapconfig snapcfgGenesis.AsyncBuild = !bc.cacheConfig.SnapshotWait var err error diff --git a/core/state/statedb.go b/core/state/statedb.go index 1454589434..1da632237d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -214,7 +214,9 @@ func (s *StateDB) StartPrefetcher(namespace string, maxConcurrency int) { } if s.snap != nil { s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) - s.prefetcher.rootTrie = s.trie + if s.db.TrieDB().Config().KeyValueDB.KVBackend != nil { + s.prefetcher.rootTrie = s.trie + } } } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index c2c4766281..b96db7a4dd 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -223,15 +223,15 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr comm } func (p *triePrefetcher) getRootTrie() Trie { - if p.rootTrie == nil { - var err error - p.rootTrie, err = p.db.OpenTrie(p.root) - if err != nil { - log.Warn("Trie prefetcher failed opening root trie", "root", p.root, "err", err) - return nil - } + if p.rootTrie != nil { + return p.rootTrie + } + rootTrie, err := p.db.OpenTrie(p.root) + if err != nil { + log.Warn("Trie prefetcher failed opening root trie", "root", p.root, "err", err) + return nil } - return p.rootTrie + return rootTrie } // trie returns the trie matching the root hash, or nil if the prefetcher doesn't diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 95caa80c1a..50409f080d 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -42,15 +42,23 @@ func getBackend(t *testing.T, name string) *reprocessBackend { } someAddr := common.Address{1} + endOfBlockStateTransition := func(block *types.Header, statedb *state.StateDB) { + i := block.Number.Uint64() + statedb.SetNonce(someAddr, i) + iBytes := binary.BigEndian.AppendUint64(nil, i) + asHash := common.BytesToHash(iBytes) + statedb.SetState(someAddr, asHash, asHash) + } + cbs := dummy.ConsensusCallbacks{ OnExtraStateChange: func(block *types.Block, statedb *state.StateDB) (*big.Int, *big.Int, error) { - i := block.Number().Uint64() - statedb.SetNonce(someAddr, i) - iBytes := binary.BigEndian.AppendUint64(nil, i) - asHash := common.BytesToHash(iBytes) - statedb.SetState(someAddr, asHash, asHash) + endOfBlockStateTransition(block.Header(), statedb) return testVM.onExtraStateChange(block, statedb) }, + OnFinalizeAndAssemble: func(header *types.Header, state *state.StateDB, txs []*types.Transaction) (extraData []byte, blockFeeContribution *big.Int, extDataGasUsed *big.Int, err error) { + endOfBlockStateTransition(header, state) + return nil, nil, nil, nil + }, } engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 35cc862063..e954be9931 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -16,7 +16,6 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/shim/merkledb" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -148,10 +147,11 @@ func TestReprocessGenesis(t *testing.T) { Tracer: trace.Noop, }) require.NoError(t, err) + _ = mdb cacheConfig := *core.DefaultCacheConfig cacheConfig.KeyValueDB = &triedb.KeyValueConfig{ - KVBackend: merkledb.NewMerkleDB(mdb), + // KVBackend: merkledb.NewMerkleDB(mdb), } cacheConfig.TriePrefetcherParallelism = 4 cacheConfig.SnapshotLimit = 256 @@ -163,7 +163,7 @@ func TestReprocessGenesis(t *testing.T) { var lastInsertedRoot common.Hash checkRootFn := func(expected, got common.Hash) bool { - t.Logf("Got root: %s", got.Hex()) + t.Logf("Got root: %x (original: %x)", got, expected) lastInsertedRoot = got return true } @@ -176,7 +176,10 @@ func TestReprocessGenesis(t *testing.T) { t.Logf("Genesis block: %s", bc.CurrentBlock().Hash().Hex()) getCurrentRoot := func() common.Hash { - return cacheConfig.KeyValueDB.KVBackend.Root() + if backend := cacheConfig.KeyValueDB.KVBackend; backend != nil { + return backend.Root() + } + return bc.CurrentHeader().Root // If backend is not specified roots must match geth implementation } lastRoot := getCurrentRoot() lastHash := normalGenesis.Hash() @@ -207,7 +210,9 @@ func TestReprocessGenesis(t *testing.T) { bc.Stop() expectedAccounts, expectedStorages := 3, int(stop) // test backend inserts 1 storage per block - checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) + if cacheConfig.SnapshotLimit > 0 { + checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) + } // Great, now let's restart the chain cacheConfig.SnapshotNoBuild = true @@ -244,7 +249,9 @@ func TestReprocessGenesis(t *testing.T) { bc.Stop() expectedAccounts, expectedStorages = 3, int(stop) // test backend inserts 1 storage per block - checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) + if cacheConfig.SnapshotLimit > 0 { + checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) + } } func checkSnapshot(t *testing.T, db ethdb.Database, expectedAccounts, expectedStorages *int, log bool) { diff --git a/shim/trie.go b/shim/trie.go index 62b4d227cb..eed6417e54 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -80,6 +80,11 @@ func (t *Trie) Get(key []byte) ([]byte, error) { } func (t *Trie) Copy() *Trie { + if legacy, ok := t.backend.(*LegacyBackend); ok { + return &Trie{ + backend: &LegacyBackend{tr: legacy.tr.Copy()}, + } + } // fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) return t } From 159f8f4b50f19d9c5dc6c2a0eab1ad04d01bd83c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 15:41:07 -0800 Subject: [PATCH 040/307] fix UTs --- core/blockchain.go | 3 ++- core/state/statedb.go | 3 ++- plugin/evm/reprocess_test.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 3297614cb3..9cefc7c392 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1930,7 +1930,8 @@ func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { AsyncBuild: asyncBuild, SkipVerify: !bc.cacheConfig.SnapshotVerify, } - if b.Number.Uint64() == 0 && bc.cacheConfig.KeyValueDB.KVBackend != nil { + kvConfig := bc.cacheConfig.KeyValueDB + if b.Number.Uint64() == 0 && kvConfig != nil && kvConfig.KVBackend != nil { snapcfgGenesis := snapconfig snapcfgGenesis.AsyncBuild = !bc.cacheConfig.SnapshotWait var err error diff --git a/core/state/statedb.go b/core/state/statedb.go index 1da632237d..3d88f391d3 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -214,7 +214,8 @@ func (s *StateDB) StartPrefetcher(namespace string, maxConcurrency int) { } if s.snap != nil { s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) - if s.db.TrieDB().Config().KeyValueDB.KVBackend != nil { + kvConfig := s.db.TrieDB().Config().KeyValueDB + if kvConfig != nil && kvConfig.KVBackend != nil { s.prefetcher.rootTrie = s.trie } } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index e954be9931..7d9cb5d154 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -60,7 +60,7 @@ func (r *prefixReader) Has(key []byte) (bool, error) { return r.Database.Has(append(r.prefix, key...)) } -func TestExportBlocks(t *testing.T) { +func ExampleExportBlocks(t *testing.T) { cache := 128 handles := 1024 From d42c028b1a4640c8a507056f9402f31ce14058b1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 15:53:23 -0800 Subject: [PATCH 041/307] patch --- plugin/evm/reprocess_test.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 7d9cb5d154..fe0aac2d81 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -60,11 +60,14 @@ func (r *prefixReader) Has(key []byte) (bool, error) { return r.Database.Has(append(r.prefix, key...)) } -func ExampleExportBlocks(t *testing.T) { +func TestExportBlocks(t *testing.T) { cache := 128 handles := 1024 sourceDb, err := rawdb.NewLevelDBDatabase(sourceDbDir, cache, handles, "", true) + if err != nil { + t.Skipf("Failed to open source database: %s", err) + } require.NoError(t, err) defer sourceDb.Close() @@ -191,15 +194,11 @@ func TestReprocessGenesis(t *testing.T) { // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) - originalParentRoot := parent.Root parent.Root = lastRoot err := bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) - // Restore parent root - parent.Root = originalParentRoot - t.Logf("Accepting block %s", block.Hash().Hex()) err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) @@ -231,15 +230,11 @@ func TestReprocessGenesis(t *testing.T) { // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) - originalParentRoot := parent.Root parent.Root = lastRoot err := bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) - // Restore parent root - parent.Root = originalParentRoot - t.Logf("Accepting block %s", block.Hash().Hex()) err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) From 2568c3c5b7691143744bad9fedf07433fe3aa303 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 17:02:11 -0800 Subject: [PATCH 042/307] improvements --- core/blockchain.go | 8 +- plugin/evm/reprocess_backend_test.go | 57 ++++++++++-- plugin/evm/reprocess_test.go | 130 +++++++++------------------ 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 9cefc7c392..d8df84c633 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1931,7 +1931,7 @@ func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { SkipVerify: !bc.cacheConfig.SnapshotVerify, } kvConfig := bc.cacheConfig.KeyValueDB - if b.Number.Uint64() == 0 && kvConfig != nil && kvConfig.KVBackend != nil { + if b.Number.Uint64() == 0 && kvConfig != nil && kvConfig.KVBackend != nil && !bc.cacheConfig.SnapshotNoBuild { snapcfgGenesis := snapconfig snapcfgGenesis.AsyncBuild = !bc.cacheConfig.SnapshotWait var err error @@ -1954,7 +1954,11 @@ func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { if err := bc.snaps.Flatten(bc.genesisBlock.Hash()); err != nil { log.Error("failed to flatten genesis snapshot", "err", err) } - rawdb.WriteSnapshotRoot(bc.db, bc.genesisBlock.Root()) + root := bc.genesisBlock.Root() + if len(opts) > 0 { + root = opts[0].LastAcceptedRoot + } + rawdb.WriteSnapshotRoot(bc.db, root) // Need to mark the snapshot completed bc.snaps.AbortGeneration() snapshot.ResetSnapshotGeneration(bc.db) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 50409f080d..7567913bac 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -1,28 +1,68 @@ package evm import ( + "context" "encoding/binary" "math/big" "testing" + "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/avalanchego/utils/units" + xmerkledb "github.com/ava-labs/avalanchego/x/merkledb" "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/shim/merkledb" + "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) type reprocessBackend struct { - Genesis *core.Genesis - Engine consensus.Engine - GetBlock func(uint64) *types.Block - BlockCount uint64 + Genesis *core.Genesis + Engine consensus.Engine + GetBlock func(uint64) *types.Block + BlockCount uint64 + CacheConfig core.CacheConfig + VerifyRoot bool +} + +func getCacheConfig(t *testing.T, name string) core.CacheConfig { + ctx := context.Background() + mdbKVStore := memdb.New() + mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ + BranchFactor: xmerkledb.BranchFactor16, + Hasher: xmerkledb.DefaultHasher, + HistoryLength: 1, + RootGenConcurrency: 0, + ValueNodeCacheSize: units.MiB, + IntermediateNodeCacheSize: units.MiB, + IntermediateWriteBufferSize: units.KiB, + IntermediateWriteBatchSize: 256 * units.KiB, + Reg: prometheus.NewRegistry(), + TraceLevel: xmerkledb.InfoTrace, + Tracer: trace.Noop, + }) + require.NoError(t, err) + _ = mdb + + cacheConfig := *core.DefaultCacheConfig + cacheConfig.KeyValueDB = &triedb.KeyValueConfig{ + KVBackend: merkledb.NewMerkleDB(mdb), + } + cacheConfig.TriePrefetcherParallelism = 4 + cacheConfig.SnapshotLimit = 256 + cacheConfig.SnapshotDelayInit = true + // cacheConfig.Pruning = false + return cacheConfig } func getBackend(t *testing.T, name string) *reprocessBackend { @@ -78,9 +118,10 @@ func getBackend(t *testing.T, name string) *reprocessBackend { require.NoError(t, err) return &reprocessBackend{ - Genesis: g, - Engine: engine, - BlockCount: uint64(len(blocks)), - GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, + Genesis: g, + Engine: engine, + BlockCount: uint64(len(blocks)), + GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, + CacheConfig: getCacheConfig(t, name), } } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index fe0aac2d81..c3452f944a 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -2,24 +2,17 @@ package evm import ( "bytes" - "context" "encoding/hex" "flag" "testing" - "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/trace" - "github.com/ava-labs/avalanchego/utils/units" - xmerkledb "github.com/ava-labs/avalanchego/x/merkledb" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/vm" - "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" - "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -131,99 +124,71 @@ func TestReprocessGenesis(t *testing.T) { backend := getBackend(t, "test") g := backend.Genesis engine := backend.Engine + cacheConfig := backend.CacheConfig db := rawdb.NewMemoryDatabase() - ctx := context.Background() - - mdbKVStore := memdb.New() - mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ - BranchFactor: xmerkledb.BranchFactor16, - Hasher: xmerkledb.DefaultHasher, - HistoryLength: 1, - RootGenConcurrency: 0, - ValueNodeCacheSize: units.MiB, - IntermediateNodeCacheSize: units.MiB, - IntermediateWriteBufferSize: units.KiB, - IntermediateWriteBatchSize: 256 * units.KiB, - Reg: prometheus.NewRegistry(), - TraceLevel: xmerkledb.InfoTrace, - Tracer: trace.Noop, - }) - require.NoError(t, err) - _ = mdb - - cacheConfig := *core.DefaultCacheConfig - cacheConfig.KeyValueDB = &triedb.KeyValueConfig{ - // KVBackend: merkledb.NewMerkleDB(mdb), - } - cacheConfig.TriePrefetcherParallelism = 4 - cacheConfig.SnapshotLimit = 256 - cacheConfig.SnapshotDelayInit = true - // cacheConfig.Pruning = false bc, err := core.NewBlockChain(db, &cacheConfig, g, engine, vm.Config{}, common.Hash{}, false) require.NoError(t, err) - var lastInsertedRoot common.Hash - checkRootFn := func(expected, got common.Hash) bool { - t.Logf("Got root: %x (original: %x)", got, expected) - lastInsertedRoot = got - return true - } - bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn - normalGenesis := g.ToBlock() require.NoError(t, bc.LoadGenesisState(normalGenesis)) - bc.InitializeSnapshots() + lastRoot := normalGenesis.Root() + lastHash := normalGenesis.Hash() + if backend := cacheConfig.KeyValueDB.KVBackend; backend != nil { + lastRoot = backend.Root() + } + bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) + bc.Stop() // Genesis was created. Stop the chain. t.Logf("Genesis block: %s", bc.CurrentBlock().Hash().Hex()) - getCurrentRoot := func() common.Hash { - if backend := cacheConfig.KeyValueDB.KVBackend; backend != nil { - return backend.Root() - } - return bc.CurrentHeader().Root // If backend is not specified roots must match geth implementation - } - lastRoot := getCurrentRoot() - lastHash := normalGenesis.Hash() start, stop := uint64(1), backend.BlockCount/2 - for i := start; i <= stop; i++ { - block := backend.GetBlock(i) - t.Logf("Block: %d, Transactions: %d, Parent State: %x", i, len(block.Transactions()), lastRoot) - - // Override parentRoot to match last state - parent := bc.GetHeaderByNumber(block.NumberU64() - 1) - parent.Root = lastRoot - - err := bc.InsertBlockManualWithParent(block, parent, true) - require.NoError(t, err) - - t.Logf("Accepting block %s", block.Hash().Hex()) - err = bc.AcceptWithRoot(block, lastInsertedRoot) - require.NoError(t, err) - - lastRoot = getCurrentRoot() - lastHash = block.Hash() + lastHash, lastRoot = reprocess(t, db, backend, lastHash, lastRoot, start, stop) + if cacheConfig.SnapshotLimit > 0 { + accounts, storages := checkSnapshot(t, db, false) + t.Logf("Iterated snapshot: Accounts: %d, Storages: %d", accounts, storages) } - bc.Stop() - expectedAccounts, expectedStorages := 3, int(stop) // test backend inserts 1 storage per block + start, stop = backend.BlockCount/2+1, backend.BlockCount + lastHash, lastRoot = reprocess(t, db, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { - checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) + accounts, storages := checkSnapshot(t, db, false) + t.Logf("Iterated snapshot: Accounts: %d, Storages: %d", accounts, storages) + } + t.Logf("Last block: %d, Last hash: %x, Last root: %x", stop, lastHash, lastRoot) +} + +func reprocess( + t *testing.T, db ethdb.Database, + backend *reprocessBackend, lastHash, lastRoot common.Hash, + start, stop uint64, +) (common.Hash, common.Hash) { + cacheConfig := backend.CacheConfig + + var lastInsertedRoot common.Hash + checkRootFn := func(expected, got common.Hash) bool { + t.Logf("Got root: %x (original: %x)", got, expected) + lastInsertedRoot = got + if backend.VerifyRoot { + return expected == got + } + return true } // Great, now let's restart the chain + cacheConfig.SnapshotDelayInit = true cacheConfig.SnapshotNoBuild = true - bc, err = core.NewBlockChain( - db, &cacheConfig, g, engine, vm.Config{}, lastHash, false, + bc, err := core.NewBlockChain( + db, &cacheConfig, backend.Genesis, backend.Engine, vm.Config{}, lastHash, false, core.Opts{LastAcceptedRoot: lastRoot}, ) require.NoError(t, err) + bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) - start, stop = backend.BlockCount/2+1, backend.BlockCount for i := start; i <= stop; i++ { block := backend.GetBlock(i) t.Logf("Block: %d, Transactions: %d, Parent State: %x", i, len(block.Transactions()), lastRoot) @@ -239,17 +204,15 @@ func TestReprocessGenesis(t *testing.T) { err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) - lastRoot = getCurrentRoot() + lastRoot = lastInsertedRoot + lastHash = block.Hash() } bc.Stop() - expectedAccounts, expectedStorages = 3, int(stop) // test backend inserts 1 storage per block - if cacheConfig.SnapshotLimit > 0 { - checkSnapshot(t, db, &expectedAccounts, &expectedStorages, false) - } + return lastHash, lastRoot } -func checkSnapshot(t *testing.T, db ethdb.Database, expectedAccounts, expectedStorages *int, log bool) { +func checkSnapshot(t *testing.T, db ethdb.Database, log bool) (int, int) { t.Helper() it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) @@ -264,9 +227,6 @@ func checkSnapshot(t *testing.T, db ethdb.Database, expectedAccounts, expectedSt t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) } } - if expectedAccounts != nil { - require.Equal(t, *expectedAccounts, accounts) - } it2 := db.NewIterator(rawdb.SnapshotStoragePrefix, nil) defer it2.Release() @@ -280,7 +240,5 @@ func checkSnapshot(t *testing.T, db ethdb.Database, expectedAccounts, expectedSt t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) } } - if expectedStorages != nil { - require.Equal(t, *expectedStorages, storages) - } + return accounts, storages } From bdc8a23f43b39d45d26cefdd28187d46a9b81409 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 17:17:05 -0800 Subject: [PATCH 043/307] inline handling of genesis to reprocess fn. --- plugin/evm/reprocess_test.go | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c3452f944a..d8d0d95707 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -122,29 +122,12 @@ func TestCalculatePrefix(t *testing.T) { func TestReprocessGenesis(t *testing.T) { backend := getBackend(t, "test") - g := backend.Genesis - engine := backend.Engine cacheConfig := backend.CacheConfig db := rawdb.NewMemoryDatabase() - bc, err := core.NewBlockChain(db, &cacheConfig, g, engine, vm.Config{}, common.Hash{}, false) - require.NoError(t, err) - - normalGenesis := g.ToBlock() - require.NoError(t, bc.LoadGenesisState(normalGenesis)) - - lastRoot := normalGenesis.Root() - lastHash := normalGenesis.Hash() - if backend := cacheConfig.KeyValueDB.KVBackend; backend != nil { - lastRoot = backend.Root() - } - - bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) - bc.Stop() // Genesis was created. Stop the chain. - t.Logf("Genesis block: %s", bc.CurrentBlock().Hash().Hex()) - - start, stop := uint64(1), backend.BlockCount/2 + var lastHash, lastRoot common.Hash + start, stop := uint64(0), backend.BlockCount/2 lastHash, lastRoot = reprocess(t, db, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { accounts, storages := checkSnapshot(t, db, false) @@ -177,14 +160,32 @@ func reprocess( return true } - // Great, now let's restart the chain + var opts []core.Opts cacheConfig.SnapshotDelayInit = true - cacheConfig.SnapshotNoBuild = true + if start > 0 { + cacheConfig.SnapshotNoBuild = true // after genesis, snapshot must already be available + opts = append(opts, core.Opts{LastAcceptedRoot: lastRoot}) // after genesis, we must specify the last root + } bc, err := core.NewBlockChain( db, &cacheConfig, backend.Genesis, backend.Engine, vm.Config{}, lastHash, false, - core.Opts{LastAcceptedRoot: lastRoot}, + opts..., ) require.NoError(t, err) + defer bc.Stop() + + if start == 0 { + // Handling the genesis block + normalGenesis := backend.Genesis.ToBlock() + require.NoError(t, bc.LoadGenesisState(normalGenesis)) + + lastRoot = normalGenesis.Root() + if backend := cacheConfig.KeyValueDB.KVBackend; backend != nil { + lastRoot = backend.Root() + } + + t.Logf("Genesis performed: hash: %x, root : %x", bc.CurrentBlock().Hash(), lastRoot) + start = 1 + } bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) @@ -207,7 +208,6 @@ func reprocess( lastRoot = lastInsertedRoot lastHash = block.Hash() } - bc.Stop() return lastHash, lastRoot } From 55da58277b6ab60284bcd9171ea4832f9212cb50 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 17:46:33 -0800 Subject: [PATCH 044/307] refactor more stuff into backend --- plugin/evm/reprocess_backend_test.go | 47 ++++++++++++++++------------ plugin/evm/reprocess_test.go | 36 +++++++++++++-------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 7567913bac..e7bce1d786 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/consensus/dummy" "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" @@ -22,6 +23,7 @@ import ( "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -33,31 +35,34 @@ type reprocessBackend struct { BlockCount uint64 CacheConfig core.CacheConfig VerifyRoot bool + Disk ethdb.Database + Name string } func getCacheConfig(t *testing.T, name string) core.CacheConfig { - ctx := context.Background() - mdbKVStore := memdb.New() - mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ - BranchFactor: xmerkledb.BranchFactor16, - Hasher: xmerkledb.DefaultHasher, - HistoryLength: 1, - RootGenConcurrency: 0, - ValueNodeCacheSize: units.MiB, - IntermediateNodeCacheSize: units.MiB, - IntermediateWriteBufferSize: units.KiB, - IntermediateWriteBatchSize: 256 * units.KiB, - Reg: prometheus.NewRegistry(), - TraceLevel: xmerkledb.InfoTrace, - Tracer: trace.Noop, - }) - require.NoError(t, err) - _ = mdb + var backend triedb.KVBackend + if name == "merkledb" { + ctx := context.Background() + mdbKVStore := memdb.New() + mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ + BranchFactor: xmerkledb.BranchFactor16, + Hasher: xmerkledb.DefaultHasher, + HistoryLength: 1, + RootGenConcurrency: 0, + ValueNodeCacheSize: units.MiB, + IntermediateNodeCacheSize: units.MiB, + IntermediateWriteBufferSize: units.KiB, + IntermediateWriteBatchSize: 256 * units.KiB, + Reg: prometheus.NewRegistry(), + TraceLevel: xmerkledb.InfoTrace, + Tracer: trace.Noop, + }) + require.NoError(t, err) + backend = merkledb.NewMerkleDB(mdb) + } cacheConfig := *core.DefaultCacheConfig - cacheConfig.KeyValueDB = &triedb.KeyValueConfig{ - KVBackend: merkledb.NewMerkleDB(mdb), - } + cacheConfig.KeyValueDB = &triedb.KeyValueConfig{KVBackend: backend} cacheConfig.TriePrefetcherParallelism = 4 cacheConfig.SnapshotLimit = 256 cacheConfig.SnapshotDelayInit = true @@ -123,5 +128,7 @@ func getBackend(t *testing.T, name string) *reprocessBackend { BlockCount: uint64(len(blocks)), GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, CacheConfig: getCacheConfig(t, name), + Disk: rawdb.NewMemoryDatabase(), + Name: name, } } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index d8d0d95707..aa57515ea4 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -103,11 +103,12 @@ func TestExportBlocks(t *testing.T) { } var ( - fujiXChainID = ids.FromStringOrPanic("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm") - fujiCChainID = ids.FromStringOrPanic("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp") - mainnetXChainID = ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM") - mainnetCChainID = ids.FromStringOrPanic("2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5") - VMDBPrefix = []byte("vm") + VMDBPrefix = []byte("vm") + fujiXChainID = ids.FromStringOrPanic("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm") + fujiCChainID = ids.FromStringOrPanic("yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp") + mainnetXChainID = ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM") + mainnetCChainID = ids.FromStringOrPanic("2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5") + mainnetAvaxAssetID = ids.FromStringOrPanic("FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z") ) func TestCalculatePrefix(t *testing.T) { @@ -121,34 +122,43 @@ func TestCalculatePrefix(t *testing.T) { } func TestReprocessGenesis(t *testing.T) { - backend := getBackend(t, "test") - cacheConfig := backend.CacheConfig + for _, backend := range []*reprocessBackend{ + getBackend(t, "merkledb"), + getBackend(t, "legacy"), + } { + t.Run(backend.Name, func(t *testing.T) { + testReprocessGenesis(t, backend) + }) + } +} - db := rawdb.NewMemoryDatabase() +func testReprocessGenesis(t *testing.T, backend *reprocessBackend) { + cacheConfig := backend.CacheConfig var lastHash, lastRoot common.Hash start, stop := uint64(0), backend.BlockCount/2 - lastHash, lastRoot = reprocess(t, db, backend, lastHash, lastRoot, start, stop) + lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { - accounts, storages := checkSnapshot(t, db, false) + accounts, storages := checkSnapshot(t, backend.Disk, false) t.Logf("Iterated snapshot: Accounts: %d, Storages: %d", accounts, storages) } start, stop = backend.BlockCount/2+1, backend.BlockCount - lastHash, lastRoot = reprocess(t, db, backend, lastHash, lastRoot, start, stop) + lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { - accounts, storages := checkSnapshot(t, db, false) + accounts, storages := checkSnapshot(t, backend.Disk, false) t.Logf("Iterated snapshot: Accounts: %d, Storages: %d", accounts, storages) } t.Logf("Last block: %d, Last hash: %x, Last root: %x", stop, lastHash, lastRoot) } func reprocess( - t *testing.T, db ethdb.Database, + t *testing.T, backend *reprocessBackend, lastHash, lastRoot common.Hash, start, stop uint64, ) (common.Hash, common.Hash) { cacheConfig := backend.CacheConfig + db := backend.Disk var lastInsertedRoot common.Hash checkRootFn := func(expected, got common.Hash) bool { From d86fc2e7ba340454b322857b81e70539ce9189c0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 17:56:48 -0800 Subject: [PATCH 045/307] try to reprocess mainnet blocks in memory --- plugin/evm/reprocess_backend_test.go | 33 +++++++++++++++++++++--- plugin/evm/reprocess_test.go | 38 ++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index e7bce1d786..659ad193a4 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -3,6 +3,7 @@ package evm import ( "context" "encoding/binary" + "encoding/json" "math/big" "testing" @@ -81,9 +82,7 @@ func getBackend(t *testing.T, name string) *reprocessBackend { testVM := &VM{ chainConfig: chainConfig, codec: Codec, - ctx: &snow.Context{ - AVAXAssetID: ids.ID{1}, - }, + ctx: &snow.Context{AVAXAssetID: ids.ID{1}}, } someAddr := common.Address{1} @@ -132,3 +131,31 @@ func getBackend(t *testing.T, name string) *reprocessBackend { Name: name, } } + +func getMainnetInMemoryBackend(t *testing.T, name string, blocks uint64, source ethdb.Database) *reprocessBackend { + var g core.Genesis + require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) + + testVM := &VM{ + chainConfig: g.Config, + codec: Codec, + ctx: &snow.Context{AVAXAssetID: mainnetAvaxAssetID}, + } + cbs := dummy.ConsensusCallbacks{OnExtraStateChange: testVM.onExtraStateChange} + engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ModeSkipHeader: true}) + + return &reprocessBackend{ + Genesis: &g, + Engine: engine, + BlockCount: blocks, + GetBlock: func(i uint64) *types.Block { + hash := rawdb.ReadCanonicalHash(source, i) + block := rawdb.ReadBlock(source, hash, i) + require.NotNil(t, block) + return block + }, + CacheConfig: getCacheConfig(t, name), + Disk: rawdb.NewMemoryDatabase(), + Name: name, + } +} diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index aa57515ea4..b64f0d69b5 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -53,17 +53,16 @@ func (r *prefixReader) Has(key []byte) (bool, error) { return r.Database.Has(append(r.prefix, key...)) } -func TestExportBlocks(t *testing.T) { - cache := 128 - handles := 1024 +const ( + cacheSize = 128 + handles = 1024 +) - sourceDb, err := rawdb.NewLevelDBDatabase(sourceDbDir, cache, handles, "", true) +func openSourceDB(t *testing.T) ethdb.Database { + sourceDb, err := rawdb.NewLevelDBDatabase(sourceDbDir, cacheSize, handles, "", true) if err != nil { t.Skipf("Failed to open source database: %s", err) } - require.NoError(t, err) - defer sourceDb.Close() - prefix := []byte(sourcePrefix) if bytes.HasPrefix(prefix, []byte("0x")) { prefix = prefix[2:] @@ -73,15 +72,19 @@ func TestExportBlocks(t *testing.T) { t.Fatalf("invalid hex prefix: %s", prefix) } } - t.Logf("Using prefix: %x", prefix) - sourceDb = &prefixReader{Database: sourceDb, prefix: prefix} + return &prefixReader{Database: sourceDb, prefix: prefix} +} + +func TestExportBlocks(t *testing.T) { + sourceDb := openSourceDB(t) + defer sourceDb.Close() if startBlock == 0 { startBlock = 1 t.Logf("Start block is 0, setting to 1") } - db, err := rawdb.NewLevelDBDatabase(dbDir, cache, handles, "", false) + db, err := rawdb.NewLevelDBDatabase(dbDir, cacheSize, handles, "", false) require.NoError(t, err) defer db.Close() @@ -132,6 +135,21 @@ func TestReprocessGenesis(t *testing.T) { } } +func TestReprocessMainnetBlocksInMemory(t *testing.T) { + source := openSourceDB(t) + defer source.Close() + + blocks := uint64(100) + for _, backend := range []*reprocessBackend{ + getMainnetInMemoryBackend(t, "merkledb", blocks, source), + getMainnetInMemoryBackend(t, "legacy", blocks, source), + } { + t.Run(backend.Name, func(t *testing.T) { + testReprocessGenesis(t, backend) + }) + } +} + func testReprocessGenesis(t *testing.T, backend *reprocessBackend) { cacheConfig := backend.CacheConfig From b581345763233568aa4e3854df8a8115bd323d52 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 17:59:55 -0800 Subject: [PATCH 046/307] use cmd flag for end --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index b64f0d69b5..b90b68b0f7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -139,7 +139,7 @@ func TestReprocessMainnetBlocksInMemory(t *testing.T) { source := openSourceDB(t) defer source.Close() - blocks := uint64(100) + blocks := endBlock for _, backend := range []*reprocessBackend{ getMainnetInMemoryBackend(t, "merkledb", blocks, source), getMainnetInMemoryBackend(t, "legacy", blocks, source), From d3eca270052ebc0eb9b7d9f415e4a9034594c300 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 18:02:17 -0800 Subject: [PATCH 047/307] try verify root --- plugin/evm/reprocess_backend_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 659ad193a4..c33912070e 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -129,6 +129,7 @@ func getBackend(t *testing.T, name string) *reprocessBackend { CacheConfig: getCacheConfig(t, name), Disk: rawdb.NewMemoryDatabase(), Name: name, + VerifyRoot: name == "legacy", } } @@ -157,5 +158,6 @@ func getMainnetInMemoryBackend(t *testing.T, name string, blocks uint64, source CacheConfig: getCacheConfig(t, name), Disk: rawdb.NewMemoryDatabase(), Name: name, + VerifyRoot: name == "legacy", } } From 23226a816f892692a97b50bf993c755d358afe05 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 18:06:13 -0800 Subject: [PATCH 048/307] try --- shim/merkledb/merkledb.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index c9d6483ba5..a847d769ea 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -65,7 +65,8 @@ func (m *MerkleDB) Commit(root common.Hash) error { return fmt.Errorf("no pending views") } pendingRootIdx := -1 - for i, pendingRoot := range m.pendingViewRoots { + for i := len(m.pendingViewRoots) - 1; i >= 0; i-- { + pendingRoot := m.pendingViewRoots[i] if pendingRoot == root { pendingRootIdx = i break From 83f9203fb6d1f741d9be06fb57ceadad5179256d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 18:12:12 -0800 Subject: [PATCH 049/307] try --- plugin/evm/reprocess_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index b90b68b0f7..e961f96d29 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "flag" + "os" "testing" "github.com/ava-labs/avalanchego/database/prefixdb" @@ -13,6 +14,7 @@ import ( "github.com/ava-labs/coreth/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -136,6 +138,7 @@ func TestReprocessGenesis(t *testing.T) { } func TestReprocessMainnetBlocksInMemory(t *testing.T) { + enableLogging() source := openSourceDB(t) defer source.Close() @@ -270,3 +273,7 @@ func checkSnapshot(t *testing.T, db ethdb.Database, log bool) (int, int) { } return accounts, storages } + +func enableLogging() { + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) +} From a3854a48f6d103cda80dacde8f3da222b2c8609b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 18:20:50 -0800 Subject: [PATCH 050/307] try --- shim/merkledb/merkledb.go | 3 +-- triedb/database.go | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index a847d769ea..c9d6483ba5 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -65,8 +65,7 @@ func (m *MerkleDB) Commit(root common.Hash) error { return fmt.Errorf("no pending views") } pendingRootIdx := -1 - for i := len(m.pendingViewRoots) - 1; i >= 0; i-- { - pendingRoot := m.pendingViewRoots[i] + for i, pendingRoot := range m.pendingViewRoots { if pendingRoot == root { pendingRootIdx = i break diff --git a/triedb/database.go b/triedb/database.go index 49303a7bea..7cd22d6b5d 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -180,6 +180,10 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n if db.preimages != nil { db.preimages.commit(false) } + kvConfig := db.config.KeyValueDB + if kvConfig != nil && kvConfig.KVBackend != nil { + return nil + } return db.backend.Update(root, parent, block, nodes, states) } From cda0386f21c000e5a8751144176e0c3e596dc08b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 18:26:58 -0800 Subject: [PATCH 051/307] try --- shim/merkledb/merkledb.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index c9d6483ba5..3a9b568628 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -3,6 +3,7 @@ package merkledb import ( "context" "fmt" + "sync" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/x/merkledb" @@ -13,6 +14,7 @@ import ( var _ triedb.KVBackend = &MerkleDB{} type MerkleDB struct { + lock sync.RWMutex db merkledb.MerkleDB pendingViews []merkledb.View pendingViewRoots []common.Hash @@ -31,6 +33,13 @@ func (m *MerkleDB) Get(key []byte) ([]byte, error) { } func (m *MerkleDB) latestView() merkledb.Trie { + m.lock.RLock() + defer m.lock.RUnlock() + + return m.latestViewLocked() +} + +func (m *MerkleDB) latestViewLocked() merkledb.Trie { if len(m.pendingViews) == 0 { return m.db } @@ -38,6 +47,9 @@ func (m *MerkleDB) latestView() merkledb.Trie { } func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { + m.lock.Lock() + defer m.lock.Unlock() + ctx := context.TODO() changes := make([]database.BatchOp, len(batch)) for i, kv := range batch { @@ -61,6 +73,9 @@ func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { } func (m *MerkleDB) Commit(root common.Hash) error { + m.lock.Lock() + defer m.lock.Unlock() + if len(m.pendingViews) == 0 { return fmt.Errorf("no pending views") } From 7878f88a074fcb7c17f5d0ea2ad373a4079a4cf0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 18:28:16 -0800 Subject: [PATCH 052/307] fix --- shim/merkledb/merkledb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index 3a9b568628..41b4692671 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -59,7 +59,7 @@ func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { Delete: len(kv.Value) == 0, } } - view, err := m.latestView().NewView(ctx, merkledb.ViewChanges{BatchOps: changes}) + view, err := m.latestViewLocked().NewView(ctx, merkledb.ViewChanges{BatchOps: changes}) if err != nil { return common.Hash{}, err } From e7252be70fa5a9a63abe04fe944646d70f077797 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 19:30:37 -0800 Subject: [PATCH 053/307] add metadata store --- plugin/evm/reprocess_backend_test.go | 21 +++---- plugin/evm/reprocess_test.go | 87 ++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index c33912070e..76a1105424 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/trace" @@ -37,14 +37,14 @@ type reprocessBackend struct { CacheConfig core.CacheConfig VerifyRoot bool Disk ethdb.Database + Metadata database.Database Name string } -func getCacheConfig(t *testing.T, name string) core.CacheConfig { +func getCacheConfig(t *testing.T, name string, mdbKVStore database.Database) core.CacheConfig { var backend triedb.KVBackend if name == "merkledb" { ctx := context.Background() - mdbKVStore := memdb.New() mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ BranchFactor: xmerkledb.BranchFactor16, Hasher: xmerkledb.DefaultHasher, @@ -66,12 +66,11 @@ func getCacheConfig(t *testing.T, name string) core.CacheConfig { cacheConfig.KeyValueDB = &triedb.KeyValueConfig{KVBackend: backend} cacheConfig.TriePrefetcherParallelism = 4 cacheConfig.SnapshotLimit = 256 - cacheConfig.SnapshotDelayInit = true // cacheConfig.Pruning = false return cacheConfig } -func getBackend(t *testing.T, name string) *reprocessBackend { +func getBackend(t *testing.T, name string, dbs dbs) *reprocessBackend { chainConfig := params.TestChainConfig key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 := crypto.PubkeyToAddress(key1.PublicKey) @@ -126,14 +125,15 @@ func getBackend(t *testing.T, name string) *reprocessBackend { Engine: engine, BlockCount: uint64(len(blocks)), GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, - CacheConfig: getCacheConfig(t, name), - Disk: rawdb.NewMemoryDatabase(), + CacheConfig: getCacheConfig(t, name, dbs.merkledb), + Disk: dbs.chain, + Metadata: dbs.metadata, Name: name, VerifyRoot: name == "legacy", } } -func getMainnetInMemoryBackend(t *testing.T, name string, blocks uint64, source ethdb.Database) *reprocessBackend { +func getMainnetBackend(t *testing.T, name string, blocks uint64, source ethdb.Database, dbs dbs) *reprocessBackend { var g core.Genesis require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) @@ -155,8 +155,9 @@ func getMainnetInMemoryBackend(t *testing.T, name string, blocks uint64, source require.NotNil(t, block) return block }, - CacheConfig: getCacheConfig(t, name), - Disk: rawdb.NewMemoryDatabase(), + CacheConfig: getCacheConfig(t, name, dbs.merkledb), + Disk: dbs.chain, + Metadata: dbs.metadata, Name: name, VerifyRoot: name == "legacy", } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index e961f96d29..f72b1b0e19 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -7,14 +7,19 @@ import ( "os" "testing" + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/database/leveldb" + "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -26,7 +31,7 @@ var ( var ( sourceDbDir = "sourceDb" sourcePrefix = "" - dbDir = "db" + dbDir = "" startBlock = uint64(0) endBlock = uint64(20_000) ) @@ -116,6 +121,65 @@ var ( mainnetAvaxAssetID = ids.FromStringOrPanic("FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z") ) +type dbs struct { + metadata database.Database + chain ethdb.Database + merkledb database.Database + + base database.Database +} + +func (d *dbs) Close() { d.base.Close() } + +func openDBs(t *testing.T) dbs { + var base database.Database + if dbDir == "" { + base = memdb.New() + } else { + db, err := leveldb.New(dbDir, nil, logging.NoLog{}, prometheus.NewRegistry()) + require.NoError(t, err) + base = db + } + + return dbs{ + metadata: prefixdb.New(reprocessMetadataPrefix, base), + chain: rawdb.NewDatabase(Database{prefixdb.New(ethDBPrefix, base)}), + merkledb: prefixdb.New(merkledbPrefix, base), + base: base, + } +} + +var ( + reprocessMetadataPrefix = []byte("metadata") + merkledbPrefix = []byte("merkledb") + + lastAcceptedRootKey = []byte("lastAcceptedRoot") + lastAcceptedHashKey = []byte("lastAcceptedHash") + lastAcceptedHeightKey = []byte("lastAcceptedHeight") +) + +func getMetadata(db database.Database) (lastHash, lastRoot common.Hash, lastHeight uint64) { + if bytes, err := db.Get(lastAcceptedRootKey); err == nil { + lastRoot = common.BytesToHash(bytes) + } + if bytes, err := db.Get(lastAcceptedHashKey); err == nil { + lastHash = common.BytesToHash(bytes) + } + if bytes, err := database.GetUInt64(db, lastAcceptedHeightKey); err == nil { + lastHeight = bytes + } + + return lastHash, lastRoot, lastHeight +} + +func TestPersistedMetadata(t *testing.T) { + dbs := openDBs(t) + defer dbs.Close() + + lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) + t.Logf("Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) +} + func TestCalculatePrefix(t *testing.T) { prefix := prefixdb.JoinPrefixes( prefixdb.MakePrefix(mainnetCChainID[:]), @@ -127,9 +191,12 @@ func TestCalculatePrefix(t *testing.T) { } func TestReprocessGenesis(t *testing.T) { + dbs := openDBs(t) + defer dbs.Close() + for _, backend := range []*reprocessBackend{ - getBackend(t, "merkledb"), - getBackend(t, "legacy"), + getBackend(t, "merkledb", dbs), + getBackend(t, "legacy", dbs), } { t.Run(backend.Name, func(t *testing.T) { testReprocessGenesis(t, backend) @@ -137,15 +204,18 @@ func TestReprocessGenesis(t *testing.T) { } } -func TestReprocessMainnetBlocksInMemory(t *testing.T) { +func TestReprocessMainnetBlocks(t *testing.T) { enableLogging() source := openSourceDB(t) defer source.Close() + dbs := openDBs(t) + defer dbs.Close() + blocks := endBlock for _, backend := range []*reprocessBackend{ - getMainnetInMemoryBackend(t, "merkledb", blocks, source), - getMainnetInMemoryBackend(t, "legacy", blocks, source), + getMainnetBackend(t, "merkledb", blocks, source, dbs), + getMainnetBackend(t, "legacy", blocks, source, dbs), } { t.Run(backend.Name, func(t *testing.T) { testReprocessGenesis(t, backend) @@ -238,6 +308,11 @@ func reprocess( lastRoot = lastInsertedRoot lastHash = block.Hash() + + // Update metadata + require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) + require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) + require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) } return lastHash, lastRoot From e83dc48dfe796d8840f07c80168274292a8663a8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 19:46:03 -0800 Subject: [PATCH 054/307] track metadata, add flags --- plugin/evm/reprocess_backend_test.go | 23 +++++++++--------- plugin/evm/reprocess_test.go | 36 ++++++++++++++++++---------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 76a1105424..838f1c1721 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -33,7 +33,6 @@ type reprocessBackend struct { Genesis *core.Genesis Engine consensus.Engine GetBlock func(uint64) *types.Block - BlockCount uint64 CacheConfig core.CacheConfig VerifyRoot bool Disk ethdb.Database @@ -64,13 +63,16 @@ func getCacheConfig(t *testing.T, name string, mdbKVStore database.Database) cor cacheConfig := *core.DefaultCacheConfig cacheConfig.KeyValueDB = &triedb.KeyValueConfig{KVBackend: backend} - cacheConfig.TriePrefetcherParallelism = 4 - cacheConfig.SnapshotLimit = 256 - // cacheConfig.Pruning = false + cacheConfig.TriePrefetcherParallelism = prefetchers + cacheConfig.SnapshotLimit = 0 + if useSnapshot { + cacheConfig.SnapshotLimit = 256 + } + cacheConfig.Pruning = pruning return cacheConfig } -func getBackend(t *testing.T, name string, dbs dbs) *reprocessBackend { +func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessBackend { chainConfig := params.TestChainConfig key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 := crypto.PubkeyToAddress(key1.PublicKey) @@ -109,7 +111,7 @@ func getBackend(t *testing.T, name string, dbs dbs) *reprocessBackend { }) signer := types.LatestSigner(chainConfig) - _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, 20, 2, func(i int, b *core.BlockGen) { + _, blocks, _, err := core.GenerateChainWithGenesis(g, engine, blocksCount, 2, func(i int, b *core.BlockGen) { tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ Nonce: uint64(i), GasPrice: b.BaseFee(), @@ -119,11 +121,11 @@ func getBackend(t *testing.T, name string, dbs dbs) *reprocessBackend { b.AddTx(tx) }) require.NoError(t, err) + require.Len(t, blocks, blocksCount) return &reprocessBackend{ Genesis: g, Engine: engine, - BlockCount: uint64(len(blocks)), GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, CacheConfig: getCacheConfig(t, name, dbs.merkledb), Disk: dbs.chain, @@ -133,7 +135,7 @@ func getBackend(t *testing.T, name string, dbs dbs) *reprocessBackend { } } -func getMainnetBackend(t *testing.T, name string, blocks uint64, source ethdb.Database, dbs dbs) *reprocessBackend { +func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs) *reprocessBackend { var g core.Genesis require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) @@ -146,9 +148,8 @@ func getMainnetBackend(t *testing.T, name string, blocks uint64, source ethdb.Da engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ModeSkipHeader: true}) return &reprocessBackend{ - Genesis: &g, - Engine: engine, - BlockCount: blocks, + Genesis: &g, + Engine: engine, GetBlock: func(i uint64) *types.Block { hash := rawdb.ReadCanonicalHash(source, i) block := rawdb.ReadBlock(source, hash, i) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index f72b1b0e19..87e3737e98 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -33,15 +33,21 @@ var ( sourcePrefix = "" dbDir = "" startBlock = uint64(0) - endBlock = uint64(20_000) + endBlock = uint64(200) + prefetchers = 4 + useSnapshot = true + pruning = true ) func TestMain(m *testing.M) { flag.StringVar(&sourceDbDir, "sourceDbDir", sourceDbDir, "directory of source database") flag.StringVar(&sourcePrefix, "sourcePrefix", sourcePrefix, "prefix of source database") - flag.StringVar(&dbDir, "dbDir", dbDir, "directory to store database") + flag.StringVar(&dbDir, "dbDir", dbDir, "directory to store database (uses memory if empty)") flag.Uint64Var(&startBlock, "startBlock", startBlock, "start block number") flag.Uint64Var(&endBlock, "endBlock", endBlock, "end block number") + flag.IntVar(&prefetchers, "prefetchers", prefetchers, "number of prefetchers") + flag.BoolVar(&useSnapshot, "useSnapshot", useSnapshot, "use snapshot") + flag.BoolVar(&pruning, "pruning", pruning, "pruning") flag.Parse() m.Run() @@ -194,12 +200,13 @@ func TestReprocessGenesis(t *testing.T) { dbs := openDBs(t) defer dbs.Close() + blocks := int(endBlock) // use the end block as the block count, since we start from 0 for _, backend := range []*reprocessBackend{ - getBackend(t, "merkledb", dbs), - getBackend(t, "legacy", dbs), + getBackend(t, "merkledb", blocks, dbs), + getBackend(t, "legacy", blocks, dbs), } { t.Run(backend.Name, func(t *testing.T) { - testReprocessGenesis(t, backend) + testReprocessGenesis(t, backend, uint64(blocks)) }) } } @@ -212,29 +219,34 @@ func TestReprocessMainnetBlocks(t *testing.T) { dbs := openDBs(t) defer dbs.Close() - blocks := endBlock + lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) + t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) + + require.Equal(t, lastHeight, startBlock, "Last height does not match start block") + for _, backend := range []*reprocessBackend{ - getMainnetBackend(t, "merkledb", blocks, source, dbs), - getMainnetBackend(t, "legacy", blocks, source, dbs), + getMainnetBackend(t, "merkledb", source, dbs), + getMainnetBackend(t, "legacy", source, dbs), } { t.Run(backend.Name, func(t *testing.T) { - testReprocessGenesis(t, backend) + lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) + t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) }) } } -func testReprocessGenesis(t *testing.T, backend *reprocessBackend) { +func testReprocessGenesis(t *testing.T, backend *reprocessBackend, blockCount uint64) { cacheConfig := backend.CacheConfig var lastHash, lastRoot common.Hash - start, stop := uint64(0), backend.BlockCount/2 + start, stop := uint64(0), blockCount/2 lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { accounts, storages := checkSnapshot(t, backend.Disk, false) t.Logf("Iterated snapshot: Accounts: %d, Storages: %d", accounts, storages) } - start, stop = backend.BlockCount/2+1, backend.BlockCount + start, stop = blockCount/2+1, blockCount lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { accounts, storages := checkSnapshot(t, backend.Disk, false) From 19fddd152a6a2131ee11cd4079844c9f214477c8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 19:51:38 -0800 Subject: [PATCH 055/307] off by one --- plugin/evm/reprocess_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 87e3737e98..f311878bdf 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -223,6 +223,10 @@ func TestReprocessMainnetBlocks(t *testing.T) { t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) require.Equal(t, lastHeight, startBlock, "Last height does not match start block") + if lastHash == (common.Hash{}) { + // Other than when genesis is not performed, start processing from the next block + startBlock++ + } for _, backend := range []*reprocessBackend{ getMainnetBackend(t, "merkledb", source, dbs), From 2f655f56c8072507e2d8cde9d2e9b193e2aa8287 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:07:39 -0800 Subject: [PATCH 056/307] need to close mdb --- plugin/evm/reprocess_backend_test.go | 69 +++++++++++++++++++--------- plugin/evm/reprocess_test.go | 4 ++ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 838f1c1721..85e3e385b5 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -38,29 +38,38 @@ type reprocessBackend struct { Disk ethdb.Database Metadata database.Database Name string + + mdb xmerkledb.MerkleDB } -func getCacheConfig(t *testing.T, name string, mdbKVStore database.Database) core.CacheConfig { - var backend triedb.KVBackend - if name == "merkledb" { - ctx := context.Background() - mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ - BranchFactor: xmerkledb.BranchFactor16, - Hasher: xmerkledb.DefaultHasher, - HistoryLength: 1, - RootGenConcurrency: 0, - ValueNodeCacheSize: units.MiB, - IntermediateNodeCacheSize: units.MiB, - IntermediateWriteBufferSize: units.KiB, - IntermediateWriteBatchSize: 256 * units.KiB, - Reg: prometheus.NewRegistry(), - TraceLevel: xmerkledb.InfoTrace, - Tracer: trace.Noop, - }) - require.NoError(t, err) - backend = merkledb.NewMerkleDB(mdb) +func (b *reprocessBackend) Close() error { + if b.mdb == nil { + return nil } + return b.mdb.Close() +} + +func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB { + ctx := context.Background() + mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ + BranchFactor: xmerkledb.BranchFactor16, + Hasher: xmerkledb.DefaultHasher, + HistoryLength: 1, + RootGenConcurrency: 0, + ValueNodeCacheSize: units.MiB, + IntermediateNodeCacheSize: units.MiB, + IntermediateWriteBufferSize: units.KiB, + IntermediateWriteBatchSize: 256 * units.KiB, + Reg: prometheus.NewRegistry(), + TraceLevel: xmerkledb.InfoTrace, + Tracer: trace.Noop, + }) + require.NoError(t, err) + return mdb +} + +func getCacheConfig(t *testing.T, name string, backend triedb.KVBackend) core.CacheConfig { cacheConfig := *core.DefaultCacheConfig cacheConfig.KeyValueDB = &triedb.KeyValueConfig{KVBackend: backend} cacheConfig.TriePrefetcherParallelism = prefetchers @@ -123,15 +132,24 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB require.NoError(t, err) require.Len(t, blocks, blocksCount) + var ( + merkleDB xmerkledb.MerkleDB + kvBackend triedb.KVBackend + ) + if name == "merkledb" { + merkleDB = getMerkleDB(t, dbs.merkledb) + kvBackend = merkledb.NewMerkleDB(merkleDB) + } return &reprocessBackend{ Genesis: g, Engine: engine, GetBlock: func(i uint64) *types.Block { return blocks[i-1] }, - CacheConfig: getCacheConfig(t, name, dbs.merkledb), + CacheConfig: getCacheConfig(t, name, kvBackend), Disk: dbs.chain, Metadata: dbs.metadata, Name: name, VerifyRoot: name == "legacy", + mdb: merkleDB, } } @@ -147,6 +165,14 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs cbs := dummy.ConsensusCallbacks{OnExtraStateChange: testVM.onExtraStateChange} engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ModeSkipHeader: true}) + var ( + merkleDB xmerkledb.MerkleDB + kvBackend triedb.KVBackend + ) + if name == "merkledb" { + merkleDB = getMerkleDB(t, dbs.merkledb) + kvBackend = merkledb.NewMerkleDB(merkleDB) + } return &reprocessBackend{ Genesis: &g, Engine: engine, @@ -156,10 +182,11 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs require.NotNil(t, block) return block }, - CacheConfig: getCacheConfig(t, name, dbs.merkledb), + CacheConfig: getCacheConfig(t, name, kvBackend), Disk: dbs.chain, Metadata: dbs.metadata, Name: name, VerifyRoot: name == "legacy", + mdb: merkleDB, } } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index f311878bdf..4a36df64c5 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -206,6 +206,8 @@ func TestReprocessGenesis(t *testing.T) { getBackend(t, "legacy", blocks, dbs), } { t.Run(backend.Name, func(t *testing.T) { + defer backend.Close() + testReprocessGenesis(t, backend, uint64(blocks)) }) } @@ -233,6 +235,8 @@ func TestReprocessMainnetBlocks(t *testing.T) { getMainnetBackend(t, "legacy", source, dbs), } { t.Run(backend.Name, func(t *testing.T) { + defer backend.Close() + lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) }) From c0a6254aa22b159351839e7c0fe7315fc21f76d3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:12:14 -0800 Subject: [PATCH 057/307] try --- plugin/evm/reprocess_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 4a36df64c5..d26c2eec85 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -235,10 +235,9 @@ func TestReprocessMainnetBlocks(t *testing.T) { getMainnetBackend(t, "legacy", source, dbs), } { t.Run(backend.Name, func(t *testing.T) { - defer backend.Close() - lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) + backend.Close() }) } } From a0459836847a1951110d7c7953674132c8709763 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:18:45 -0800 Subject: [PATCH 058/307] try --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index d26c2eec85..f52196fa57 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -225,7 +225,7 @@ func TestReprocessMainnetBlocks(t *testing.T) { t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) require.Equal(t, lastHeight, startBlock, "Last height does not match start block") - if lastHash == (common.Hash{}) { + if lastHash != (common.Hash{}) { // Other than when genesis is not performed, start processing from the next block startBlock++ } From 17ab510692664e8be2ea4e057667d1fbe7a8106f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:29:01 -0800 Subject: [PATCH 059/307] try --- plugin/evm/reprocess_backend_test.go | 4 +++- plugin/evm/reprocess_test.go | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 85e3e385b5..0499eb1662 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -46,7 +46,9 @@ func (b *reprocessBackend) Close() error { if b.mdb == nil { return nil } - return b.mdb.Close() + mdb := b.mdb + b.mdb = nil + return mdb.Close() } func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB { diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index f52196fa57..2414e9a7b7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -5,6 +5,9 @@ import ( "encoding/hex" "flag" "os" + "os/signal" + "sync" + "syscall" "testing" "github.com/ava-labs/avalanchego/database" @@ -206,9 +209,25 @@ func TestReprocessGenesis(t *testing.T) { getBackend(t, "legacy", blocks, dbs), } { t.Run(backend.Name, func(t *testing.T) { - defer backend.Close() + var wg sync.WaitGroup + wg.Add(1) + + // Channel to listen for OS signals + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + + // Goroutine to handle signals + go func() { + defer wg.Done() + <-sigs + t.Log("Performing cleanup...") + // Perform cleanup here + require.NoError(t, backend.Close()) + }() testReprocessGenesis(t, backend, uint64(blocks)) + close(sigs) + wg.Wait() }) } } From 7d1e9f64ccda3fac8527a77b1c92d416e034b836 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:34:40 -0800 Subject: [PATCH 060/307] try --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 2414e9a7b7..8dafb8fdb7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -214,7 +214,7 @@ func TestReprocessGenesis(t *testing.T) { // Channel to listen for OS signals sigs := make(chan os.Signal, 1) - signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) + signal.Notify(sigs, syscall.SIGINT) // Goroutine to handle signals go func() { From f29efd60111ee863613f4555cfbb82ce13a857ae Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:44:38 -0800 Subject: [PATCH 061/307] try --- plugin/evm/reprocess_test.go | 54 +++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 8dafb8fdb7..e134af1aa2 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -7,7 +7,6 @@ import ( "os" "os/signal" "sync" - "syscall" "testing" "github.com/ava-labs/avalanchego/database" @@ -199,6 +198,39 @@ func TestCalculatePrefix(t *testing.T) { t.Logf("Prefix: %x", prefix) } +func init() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go cleanupOnInterrupt(c) +} + +var cf struct { + o sync.Once + m sync.RWMutex + f []func() +} + +// cleanupOnInterrupt registers a signal handler and will execute a stack of functions if an interrupt signal is caught +func cleanupOnInterrupt(c chan os.Signal) { + for range c { + cf.o.Do(func() { + cf.m.RLock() + defer cf.m.RUnlock() + for i := len(cf.f) - 1; i >= 0; i-- { + cf.f[i]() + } + os.Exit(1) + }) + } +} + +// CleanupOnInterrupt stores cleanup functions to execute if an interrupt signal is caught +func CleanupOnInterrupt(cleanup func()) { + cf.m.Lock() + defer cf.m.Unlock() + cf.f = append(cf.f, cleanup) +} + func TestReprocessGenesis(t *testing.T) { dbs := openDBs(t) defer dbs.Close() @@ -209,25 +241,9 @@ func TestReprocessGenesis(t *testing.T) { getBackend(t, "legacy", blocks, dbs), } { t.Run(backend.Name, func(t *testing.T) { - var wg sync.WaitGroup - wg.Add(1) - - // Channel to listen for OS signals - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT) - - // Goroutine to handle signals - go func() { - defer wg.Done() - <-sigs - t.Log("Performing cleanup...") - // Perform cleanup here - require.NoError(t, backend.Close()) - }() - + defer backend.Close() + CleanupOnInterrupt(func() { backend.Close() }) testReprocessGenesis(t, backend, uint64(blocks)) - close(sigs) - wg.Wait() }) } } From 7f75d1ece4563f188ca0dabc46f03e0aff5c90e1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:51:46 -0800 Subject: [PATCH 062/307] try --- plugin/evm/reprocess_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index e134af1aa2..346bf111d8 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -242,7 +242,10 @@ func TestReprocessGenesis(t *testing.T) { } { t.Run(backend.Name, func(t *testing.T) { defer backend.Close() - CleanupOnInterrupt(func() { backend.Close() }) + CleanupOnInterrupt(func() { + t.Logf("Cleaning up %s", backend.Name) + require.NoError(t, backend.Close()) + }) testReprocessGenesis(t, backend, uint64(blocks)) }) } From 5ebdd581e51b362d5a98bb655a9b4152ffde9141 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:53:38 -0800 Subject: [PATCH 063/307] oops --- plugin/evm/reprocess_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 346bf111d8..60e2613b60 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -242,10 +242,6 @@ func TestReprocessGenesis(t *testing.T) { } { t.Run(backend.Name, func(t *testing.T) { defer backend.Close() - CleanupOnInterrupt(func() { - t.Logf("Cleaning up %s", backend.Name) - require.NoError(t, backend.Close()) - }) testReprocessGenesis(t, backend, uint64(blocks)) }) } @@ -273,9 +269,15 @@ func TestReprocessMainnetBlocks(t *testing.T) { getMainnetBackend(t, "legacy", source, dbs), } { t.Run(backend.Name, func(t *testing.T) { + defer backend.Close() + + CleanupOnInterrupt(func() { + t.Logf("Cleaning up %s", backend.Name) + require.NoError(t, backend.Close()) + }) + lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) - backend.Close() }) } } From a31dfb05fbf0f3320037061ec77c4b3305ed5ce5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:55:43 -0800 Subject: [PATCH 064/307] try --- plugin/evm/reprocess_backend_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 0499eb1662..732f277e0e 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/binary" "encoding/json" + "fmt" "math/big" "testing" @@ -48,6 +49,7 @@ func (b *reprocessBackend) Close() error { } mdb := b.mdb b.mdb = nil + fmt.Println("Closing MerkleDB") return mdb.Close() } From 69b9f595526d2c4b584d72c2771e161b0bb9d1c9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 20:57:43 -0800 Subject: [PATCH 065/307] try --- plugin/evm/reprocess_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 60e2613b60..ad76895fc0 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -369,9 +369,11 @@ func reprocess( lastHash = block.Hash() // Update metadata - require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) - require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) - require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) + if i%bc.CacheConfig().CommitInterval == 0 || bc.CacheConfig().Pruning == false { + require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) + require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) + require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) + } } return lastHash, lastRoot From 426baedd9e379108818fc5eefc2131338bb3d21d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:01:59 -0800 Subject: [PATCH 066/307] modify log --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index ad76895fc0..a4b0ad6b74 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -361,7 +361,7 @@ func reprocess( err := bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) - t.Logf("Accepting block %s", block.Hash().Hex()) + t.Logf("Accepting block %d, was inserted with root %x", i, lastInsertedRoot) err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) From 8ea65e7df079b5fc71a4d2a864dc074239ac3565 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:07:18 -0800 Subject: [PATCH 067/307] add log --- core/blockchain.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/blockchain.go b/core/blockchain.go index d8df84c633..1297217e47 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1995,6 +1995,7 @@ func (bc *BlockChain) reprocessState(current *types.Block, currentRoot common.Ha if currentRoot != (common.Hash{}) { root = currentRoot } + log.Info("Looking for state", "root", root, "acceptorTip", acceptorTip, "acceptorTipUpToDate", acceptorTipUpToDate) if bc.HasState(root) && acceptorTipUpToDate { log.Info("Skipping state reprocessing", "root", current.Root()) return nil From bbf32ce4268756b82515eca1a1f112122bf5e3d6 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:14:26 -0800 Subject: [PATCH 068/307] try --- plugin/evm/reprocess_backend_test.go | 9 +-------- plugin/evm/reprocess_test.go | 14 ++++---------- shim/merkledb/merkledb.go | 17 +++++++++++++++++ triedb/database.go | 6 ++++++ 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 732f277e0e..0928a785e1 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/binary" "encoding/json" - "fmt" "math/big" "testing" @@ -44,13 +43,7 @@ type reprocessBackend struct { } func (b *reprocessBackend) Close() error { - if b.mdb == nil { - return nil - } - mdb := b.mdb - b.mdb = nil - fmt.Println("Closing MerkleDB") - return mdb.Close() + return nil } func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB { diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index a4b0ad6b74..13558feb8f 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -271,11 +271,6 @@ func TestReprocessMainnetBlocks(t *testing.T) { t.Run(backend.Name, func(t *testing.T) { defer backend.Close() - CleanupOnInterrupt(func() { - t.Logf("Cleaning up %s", backend.Name) - require.NoError(t, backend.Close()) - }) - lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) }) @@ -332,6 +327,7 @@ func reprocess( ) require.NoError(t, err) defer bc.Stop() + CleanupOnInterrupt(bc.Stop) if start == 0 { // Handling the genesis block @@ -369,11 +365,9 @@ func reprocess( lastHash = block.Hash() // Update metadata - if i%bc.CacheConfig().CommitInterval == 0 || bc.CacheConfig().Pruning == false { - require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) - require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) - require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) - } + require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) + require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) + require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) } return lastHash, lastRoot diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index 41b4692671..50062a98dc 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -118,3 +118,20 @@ func (m *MerkleDB) Root() common.Hash { } return common.Hash(root) } + +func (m *MerkleDB) Close() error { + last := common.Hash{} + m.lock.Lock() + if len(m.pendingViewRoots) > 0 { + last = m.pendingViewRoots[len(m.pendingViewRoots)-1] + } + m.lock.Unlock() + + if last != (common.Hash{}) { + if err := m.Commit(last); err != nil { + return err + } + } + + return m.db.Close() +} diff --git a/triedb/database.go b/triedb/database.go index 7cd22d6b5d..7f18e7eaa4 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -54,6 +54,8 @@ type KVBackend interface { // This may be implemented as no-op if Update already persists changes, or // commits happen on a rolling basis. Commit(root common.Hash) error + + Close() error } type KeyValueConfig struct { @@ -238,6 +240,10 @@ func (db *Database) Scheme() string { // resources held can be released correctly. func (db *Database) Close() error { db.WritePreimages() + kvConfig := db.config.KeyValueDB + if kvConfig != nil && kvConfig.KVBackend != nil { + return kvConfig.KVBackend.Close() + } return db.backend.Close() } From 4361cfb0b681f4ca350dc98543b78e0203312cac Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:18:56 -0800 Subject: [PATCH 069/307] add hash --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 13558feb8f..4ddbcb772e 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -357,7 +357,7 @@ func reprocess( err := bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) - t.Logf("Accepting block %d, was inserted with root %x", i, lastInsertedRoot) + t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) err = bc.AcceptWithRoot(block, lastInsertedRoot) require.NoError(t, err) From 191524722e42d7f758d1f5c95d46f0adfee77886 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:25:53 -0800 Subject: [PATCH 070/307] try --- core/blockchain.go | 13 ++++++++++--- plugin/evm/reprocess_test.go | 13 ++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 1297217e47..93b79f1802 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -632,19 +632,20 @@ func (bc *BlockChain) startAcceptor() { // addAcceptorQueue adds a new *types.Block to the [acceptorQueue]. This will // block if there are [AcceptorQueueLimit] items in [acceptorQueue]. -func (bc *BlockChain) addAcceptorQueue(b *blockRoot) { +func (bc *BlockChain) addAcceptorQueue(b *blockRoot) bool { // We only acquire a read lock here because it is ok to add items to the // [acceptorQueue] concurrently. bc.acceptorClosingLock.RLock() defer bc.acceptorClosingLock.RUnlock() if bc.acceptorClosed { - return + return false } acceptorQueueGauge.Inc(1) bc.acceptorWg.Add(1) bc.acceptorQueue <- b + return true } // DrainAcceptorQueue blocks until all items in [acceptorQueue] have been @@ -1085,6 +1086,8 @@ func (bc *BlockChain) Accept(block *types.Block) error { return bc.AcceptWithRoot(block, block.Root()) } +var ErrAcceptorClosed = errors.New("acceptor closed") + func (bc *BlockChain) AcceptWithRoot(block *types.Block, root common.Hash) error { bc.chainmu.Lock() defer bc.chainmu.Unlock() @@ -1112,7 +1115,11 @@ func (bc *BlockChain) AcceptWithRoot(block *types.Block, root common.Hash) error // Enqueue block in the acceptor bc.lastAccepted = block - bc.addAcceptorQueue(&blockRoot{Block: block, root: root}) + added := bc.addAcceptorQueue(&blockRoot{Block: block, root: root}) + if !added { + return ErrAcceptorClosed + } + acceptedBlockGasUsedCounter.Inc(int64(block.GasUsed())) acceptedTxsCounter.Inc(int64(len(block.Transactions()))) return nil diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 4ddbcb772e..c0555112cb 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -327,7 +327,15 @@ func reprocess( ) require.NoError(t, err) defer bc.Stop() - CleanupOnInterrupt(bc.Stop) + + var lock sync.Mutex + + CleanupOnInterrupt(func() { + lock.Lock() + defer lock.Unlock() + + bc.Stop() + }) if start == 0 { // Handling the genesis block @@ -354,6 +362,8 @@ func reprocess( parent := bc.GetHeaderByNumber(block.NumberU64() - 1) parent.Root = lastRoot + // Take lock here to prevent shutdown before block is accepted + lock.Lock() err := bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) @@ -368,6 +378,7 @@ func reprocess( require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) + lock.Unlock() } return lastHash, lastRoot From a306b81efcd8a0e5ad60816e46476c5a86e54d3e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:32:15 -0800 Subject: [PATCH 071/307] try --- plugin/evm/reprocess_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c0555112cb..0a9cd353a7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "sync" + "syscall" "testing" "github.com/ava-labs/avalanchego/database" @@ -200,7 +201,7 @@ func TestCalculatePrefix(t *testing.T) { func init() { c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) go cleanupOnInterrupt(c) } From ce5b773c263fe0d71705a9104c648d0984ecc624 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:35:19 -0800 Subject: [PATCH 072/307] try --- plugin/evm/reprocess_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 0a9cd353a7..671b77b613 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -200,6 +200,8 @@ func TestCalculatePrefix(t *testing.T) { } func init() { + syscall.Setsid() // Creates a new process group + c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go cleanupOnInterrupt(c) From 514042a3c284f9d2849a9e78532bcf1cb4a05225 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:37:16 -0800 Subject: [PATCH 073/307] try --- plugin/evm/reprocess_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 671b77b613..88e0c79d37 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -200,8 +200,6 @@ func TestCalculatePrefix(t *testing.T) { } func init() { - syscall.Setsid() // Creates a new process group - c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go cleanupOnInterrupt(c) @@ -339,6 +337,7 @@ func reprocess( bc.Stop() }) + syscall.Setsid() // Creates a new process group if start == 0 { // Handling the genesis block From 4745972561c443fc7e489c130d6cf92e4acdbfc4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:40:13 -0800 Subject: [PATCH 074/307] try --- plugin/evm/reprocess_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 88e0c79d37..0a9cd353a7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -337,7 +337,6 @@ func reprocess( bc.Stop() }) - syscall.Setsid() // Creates a new process group if start == 0 { // Handling the genesis block From 515ce06594354f143f39aba3da356b7c562fd870 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 18 Dec 2024 21:43:31 -0800 Subject: [PATCH 075/307] try --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 0a9cd353a7..132cef833e 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -201,7 +201,7 @@ func TestCalculatePrefix(t *testing.T) { func init() { c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGPIPE, syscall.SIGHUP) go cleanupOnInterrupt(c) } From 6fea3bb8a9e39d82b920cdbf51b00136204be1eb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 07:44:43 -0800 Subject: [PATCH 076/307] add logging for atomic tx --- plugin/evm/reprocess_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 132cef833e..b25c71a144 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -357,7 +357,10 @@ func reprocess( for i := start; i <= stop; i++ { block := backend.GetBlock(i) - t.Logf("Block: %d, Transactions: %d, Parent State: %x", i, len(block.Transactions()), lastRoot) + isApricotPhase5 := backend.Genesis.Config.IsApricotPhase5(block.Time()) + atomicTxs, err := ExtractAtomicTxs(block.ExtData(), isApricotPhase5, Codec) + require.NoError(t, err) + t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot) // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) @@ -365,7 +368,7 @@ func reprocess( // Take lock here to prevent shutdown before block is accepted lock.Lock() - err := bc.InsertBlockManualWithParent(block, parent, true) + err = bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) From 9a940ba86d4563d598c860f05fd2acfa3351018c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 07:46:52 -0800 Subject: [PATCH 077/307] use term string --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index b25c71a144..75d54d0687 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -360,7 +360,7 @@ func reprocess( isApricotPhase5 := backend.Genesis.Config.IsApricotPhase5(block.Time()) atomicTxs, err := ExtractAtomicTxs(block.ExtData(), isApricotPhase5, Codec) require.NoError(t, err) - t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot) + t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) From ac5230e3626fcaca2700575e59598b69e0e4d3b7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 08:54:10 -0800 Subject: [PATCH 078/307] fix UTs --- core/blockchain.go | 5 +---- plugin/evm/reprocess_backend_test.go | 4 ---- plugin/evm/reprocess_test.go | 25 ++++++++++--------------- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 93b79f1802..2657255fdb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1939,10 +1939,8 @@ func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { } kvConfig := bc.cacheConfig.KeyValueDB if b.Number.Uint64() == 0 && kvConfig != nil && kvConfig.KVBackend != nil && !bc.cacheConfig.SnapshotNoBuild { - snapcfgGenesis := snapconfig - snapcfgGenesis.AsyncBuild = !bc.cacheConfig.SnapshotWait var err error - bc.snaps, err = snapshot.New(snapcfgGenesis, bc.db, bc.triedb, common.Hash{}, types.EmptyRootHash) + bc.snaps, err = snapshot.New(snapconfig, bc.db, bc.triedb, common.Hash{}, types.EmptyRootHash) if err != nil { log.Error("failed to initialize snapshots", "headRoot", b.Root, "err", err, "async", asyncBuild) } @@ -1967,7 +1965,6 @@ func (bc *BlockChain) initSnapshot(b *types.Header, opts ...*Opts) { } rawdb.WriteSnapshotRoot(bc.db, root) // Need to mark the snapshot completed - bc.snaps.AbortGeneration() snapshot.ResetSnapshotGeneration(bc.db) } var err error diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 0928a785e1..7d7c37b89e 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -42,10 +42,6 @@ type reprocessBackend struct { mdb xmerkledb.MerkleDB } -func (b *reprocessBackend) Close() error { - return nil -} - func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB { ctx := context.Background() mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 75d54d0687..0dce19e0ff 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -233,18 +233,8 @@ func CleanupOnInterrupt(cleanup func()) { } func TestReprocessGenesis(t *testing.T) { - dbs := openDBs(t) - defer dbs.Close() - - blocks := int(endBlock) // use the end block as the block count, since we start from 0 - for _, backend := range []*reprocessBackend{ - getBackend(t, "merkledb", blocks, dbs), - getBackend(t, "legacy", blocks, dbs), - } { - t.Run(backend.Name, func(t *testing.T) { - defer backend.Close() - testReprocessGenesis(t, backend, uint64(blocks)) - }) + for _, backend := range []string{"merkledb", "legacy"} { + t.Run(backend, func(t *testing.T) { testReprocessGenesis(t, backend) }) } } @@ -270,15 +260,18 @@ func TestReprocessMainnetBlocks(t *testing.T) { getMainnetBackend(t, "legacy", source, dbs), } { t.Run(backend.Name, func(t *testing.T) { - defer backend.Close() - lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) }) } } -func testReprocessGenesis(t *testing.T, backend *reprocessBackend, blockCount uint64) { +func testReprocessGenesis(t *testing.T, backendName string) { + dbs := openDBs(t) + defer dbs.Close() + + blockCount := endBlock // use the end block as the block count, since we start from 0 + backend := getBackend(t, backendName, int(blockCount), dbs) cacheConfig := backend.CacheConfig var lastHash, lastRoot common.Hash @@ -289,6 +282,8 @@ func testReprocessGenesis(t *testing.T, backend *reprocessBackend, blockCount ui t.Logf("Iterated snapshot: Accounts: %d, Storages: %d", accounts, storages) } + // Need to re-open backend as the previous one is closed + backend = getBackend(t, backendName, int(blockCount), dbs) start, stop = blockCount/2+1, blockCount lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, start, stop) if cacheConfig.SnapshotLimit > 0 { From a955bb809ff22bf38e1bb5d67c4faf38d77cbcf4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 09:44:18 -0800 Subject: [PATCH 079/307] fix UT again --- core/blockchain.go | 8 +++++--- plugin/evm/reprocess_test.go | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 2657255fdb..59d337d590 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1083,12 +1083,14 @@ func (bc *BlockChain) LastAcceptedBlock() *types.Block { // // Assumes [bc.chainmu] is not held by the caller. func (bc *BlockChain) Accept(block *types.Block) error { - return bc.AcceptWithRoot(block, block.Root()) + // in normal operation, don't error on closed as we can recover on restart + errorOnClosed := false + return bc.AcceptWithRoot(block, block.Root(), errorOnClosed) } var ErrAcceptorClosed = errors.New("acceptor closed") -func (bc *BlockChain) AcceptWithRoot(block *types.Block, root common.Hash) error { +func (bc *BlockChain) AcceptWithRoot(block *types.Block, root common.Hash, errorOnClosed bool) error { bc.chainmu.Lock() defer bc.chainmu.Unlock() @@ -1116,7 +1118,7 @@ func (bc *BlockChain) AcceptWithRoot(block *types.Block, root common.Hash) error // Enqueue block in the acceptor bc.lastAccepted = block added := bc.addAcceptorQueue(&blockRoot{Block: block, root: root}) - if !added { + if !added && errorOnClosed { return ErrAcceptorClosed } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 0dce19e0ff..9fd4ae6ba5 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -367,7 +367,8 @@ func reprocess( require.NoError(t, err) t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) - err = bc.AcceptWithRoot(block, lastInsertedRoot) + errorOnClosed := true // make sure block is accepted + err = bc.AcceptWithRoot(block, lastInsertedRoot, errorOnClosed) require.NoError(t, err) lastRoot = lastInsertedRoot From 9d3a9a4d1c57b01efbb33476ce78fe7ba7f63205 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 12:49:07 -0800 Subject: [PATCH 080/307] add snapshot iteration as test --- plugin/evm/reprocess_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 9fd4ae6ba5..fdb06fb9fc 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -384,6 +384,14 @@ func reprocess( return lastHash, lastRoot } +func TestCheckSnapshot(t *testing.T) { + dbs := openDBs(t) + defer dbs.Close() + + accounts, storages := checkSnapshot(t, dbs.chain, false) + t.Logf("Snapshot: Accounts: %d, Storages: %d", accounts, storages) +} + func checkSnapshot(t *testing.T, db ethdb.Database, log bool) (int, int) { t.Helper() From 6369319e78cd1b69b3544b48cce4b48e4a95adf9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 14:20:54 -0800 Subject: [PATCH 081/307] add merkledb flags --- plugin/evm/reprocess_backend_test.go | 10 +++++----- plugin/evm/reprocess_test.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 7d7c37b89e..1541256c78 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -45,14 +45,14 @@ type reprocessBackend struct { func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB { ctx := context.Background() mdb, err := xmerkledb.New(ctx, mdbKVStore, xmerkledb.Config{ - BranchFactor: xmerkledb.BranchFactor16, + BranchFactor: xmerkledb.BranchFactor(merkleDBBranchFactor), Hasher: xmerkledb.DefaultHasher, HistoryLength: 1, RootGenConcurrency: 0, - ValueNodeCacheSize: units.MiB, - IntermediateNodeCacheSize: units.MiB, - IntermediateWriteBufferSize: units.KiB, - IntermediateWriteBatchSize: 256 * units.KiB, + ValueNodeCacheSize: uint(valueNodeCacheSizeKB) * units.KiB, + IntermediateNodeCacheSize: uint(intermediateNodeCacheSizeKB) * units.KiB, + IntermediateWriteBufferSize: uint(intermediateWriteBufferSizeKB) * units.KiB, + IntermediateWriteBatchSize: uint(intermediateWriteBatchSizeKB) * units.KiB, Reg: prometheus.NewRegistry(), TraceLevel: xmerkledb.InfoTrace, Tracer: trace.Noop, diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index fdb06fb9fc..8cd42bb0df 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -40,6 +40,13 @@ var ( prefetchers = 4 useSnapshot = true pruning = true + + // merkledb options + merkleDBBranchFactor = 16 + valueNodeCacheSizeKB = 1024 + intermediateNodeCacheSizeKB = 1024 + intermediateWriteBufferSizeKB = 1024 + intermediateWriteBatchSizeKB = 256 ) func TestMain(m *testing.M) { @@ -52,6 +59,13 @@ func TestMain(m *testing.M) { flag.BoolVar(&useSnapshot, "useSnapshot", useSnapshot, "use snapshot") flag.BoolVar(&pruning, "pruning", pruning, "pruning") + // merkledb options + flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") + flag.IntVar(&valueNodeCacheSizeKB, "valueNodeCacheSizeKB", valueNodeCacheSizeKB, "value node cache size in KB") + flag.IntVar(&intermediateNodeCacheSizeKB, "intermediateNodeCacheSizeKB", intermediateNodeCacheSizeKB, "intermediate node cache size in KB") + flag.IntVar(&intermediateWriteBufferSizeKB, "intermediateWriteBufferSizeKB", intermediateWriteBufferSizeKB, "intermediate write buffer size in KB") + flag.IntVar(&intermediateWriteBatchSizeKB, "intermediateWriteBatchSizeKB", intermediateWriteBatchSizeKB, "intermediate write batch size in KB") + flag.Parse() m.Run() } From 30105d47133fa88f2875fbfc59d6c87376474858 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 14:23:27 -0800 Subject: [PATCH 082/307] change units --- plugin/evm/reprocess_backend_test.go | 4 ++-- plugin/evm/reprocess_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 1541256c78..5210a70e19 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -49,8 +49,8 @@ func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB Hasher: xmerkledb.DefaultHasher, HistoryLength: 1, RootGenConcurrency: 0, - ValueNodeCacheSize: uint(valueNodeCacheSizeKB) * units.KiB, - IntermediateNodeCacheSize: uint(intermediateNodeCacheSizeKB) * units.KiB, + ValueNodeCacheSize: uint(valueNodeCacheSizeMB) * units.MiB, + IntermediateNodeCacheSize: uint(intermediateNodeCacheSizeMB) * units.MiB, IntermediateWriteBufferSize: uint(intermediateWriteBufferSizeKB) * units.KiB, IntermediateWriteBatchSize: uint(intermediateWriteBatchSizeKB) * units.KiB, Reg: prometheus.NewRegistry(), diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 8cd42bb0df..0e8bea01ac 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -43,8 +43,8 @@ var ( // merkledb options merkleDBBranchFactor = 16 - valueNodeCacheSizeKB = 1024 - intermediateNodeCacheSizeKB = 1024 + valueNodeCacheSizeMB = 1 + intermediateNodeCacheSizeMB = 1 intermediateWriteBufferSizeKB = 1024 intermediateWriteBatchSizeKB = 256 ) @@ -61,8 +61,8 @@ func TestMain(m *testing.M) { // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") - flag.IntVar(&valueNodeCacheSizeKB, "valueNodeCacheSizeKB", valueNodeCacheSizeKB, "value node cache size in KB") - flag.IntVar(&intermediateNodeCacheSizeKB, "intermediateNodeCacheSizeKB", intermediateNodeCacheSizeKB, "intermediate node cache size in KB") + flag.IntVar(&valueNodeCacheSizeMB, "valueNodeCacheSizeKB", valueNodeCacheSizeMB, "value node cache size in MB") + flag.IntVar(&intermediateNodeCacheSizeMB, "intermediateNodeCacheSizeKB", intermediateNodeCacheSizeMB, "intermediate node cache size in MB") flag.IntVar(&intermediateWriteBufferSizeKB, "intermediateWriteBufferSizeKB", intermediateWriteBufferSizeKB, "intermediate write buffer size in KB") flag.IntVar(&intermediateWriteBatchSizeKB, "intermediateWriteBatchSizeKB", intermediateWriteBatchSizeKB, "intermediate write batch size in KB") From 1ee79b5e8c9223ad939088035ca0c27c25f43032 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 14:29:15 -0800 Subject: [PATCH 083/307] fix options --- plugin/evm/reprocess_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 0e8bea01ac..fd40f0c69e 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -61,8 +61,8 @@ func TestMain(m *testing.M) { // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") - flag.IntVar(&valueNodeCacheSizeMB, "valueNodeCacheSizeKB", valueNodeCacheSizeMB, "value node cache size in MB") - flag.IntVar(&intermediateNodeCacheSizeMB, "intermediateNodeCacheSizeKB", intermediateNodeCacheSizeMB, "intermediate node cache size in MB") + flag.IntVar(&valueNodeCacheSizeMB, "valueNodeCacheSizeMB", valueNodeCacheSizeMB, "value node cache size in MB") + flag.IntVar(&intermediateNodeCacheSizeMB, "intermediateNodeCacheSizeMB", intermediateNodeCacheSizeMB, "intermediate node cache size in MB") flag.IntVar(&intermediateWriteBufferSizeKB, "intermediateWriteBufferSizeKB", intermediateWriteBufferSizeKB, "intermediate write buffer size in KB") flag.IntVar(&intermediateWriteBatchSizeKB, "intermediateWriteBatchSizeKB", intermediateWriteBatchSizeKB, "intermediate write batch size in KB") From 74f8e5bf268c1713ad4167a161d83e5436d0ead0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 15:45:10 -0800 Subject: [PATCH 084/307] support upgrades --- plugin/evm/reprocess_backend_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 5210a70e19..cc79a2ff0c 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/trace" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/utils/units" xmerkledb "github.com/ava-labs/avalanchego/x/merkledb" "github.com/ava-labs/coreth/consensus" @@ -149,6 +150,8 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs) *reprocessBackend { var g core.Genesis require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) + // Update the chain config with mainnet upgrades + g.Config = params.GetChainConfig(upgrade.Mainnet, g.Config.ChainID) testVM := &VM{ chainConfig: g.Config, From 8237a659e58c0245bcc36247d7edb62e5d4a5f44 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 15:46:59 -0800 Subject: [PATCH 085/307] support skipUpgradeCheck --- plugin/evm/reprocess_test.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index fd40f0c69e..f16f7afcf7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -32,14 +32,15 @@ var ( ) var ( - sourceDbDir = "sourceDb" - sourcePrefix = "" - dbDir = "" - startBlock = uint64(0) - endBlock = uint64(200) - prefetchers = 4 - useSnapshot = true - pruning = true + sourceDbDir = "sourceDb" + sourcePrefix = "" + dbDir = "" + startBlock = uint64(0) + endBlock = uint64(200) + prefetchers = 4 + useSnapshot = true + pruning = true + skipUpgradeCheck = false // merkledb options merkleDBBranchFactor = 16 @@ -58,6 +59,7 @@ func TestMain(m *testing.M) { flag.IntVar(&prefetchers, "prefetchers", prefetchers, "number of prefetchers") flag.BoolVar(&useSnapshot, "useSnapshot", useSnapshot, "use snapshot") flag.BoolVar(&pruning, "pruning", pruning, "pruning") + flag.BoolVar(&skipUpgradeCheck, "skipUpgradeCheck", skipUpgradeCheck, "skip upgrade check") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -332,7 +334,7 @@ func reprocess( opts = append(opts, core.Opts{LastAcceptedRoot: lastRoot}) // after genesis, we must specify the last root } bc, err := core.NewBlockChain( - db, &cacheConfig, backend.Genesis, backend.Engine, vm.Config{}, lastHash, false, + db, &cacheConfig, backend.Genesis, backend.Engine, vm.Config{}, lastHash, skipUpgradeCheck, opts..., ) require.NoError(t, err) From 021fc39b67ad60bc065f8b813df8f62a980a5b42 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 17:02:17 -0800 Subject: [PATCH 086/307] decouple prefetcher from snap --- core/state/database.go | 31 +++++++++++++++++++++++++------ core/state/statedb.go | 10 ++++------ core/state/trie_prefetcher.go | 4 ++-- internal/ethapi/api.go | 2 +- shim/geth_backend.go | 3 ++- shim/kv_backend.go | 5 +++++ shim/merkledb/merkledb.go | 4 ++++ shim/secure_trie.go | 10 ++++++++++ shim/trie.go | 6 ++++++ triedb/database.go | 5 +++++ 10 files changed, 64 insertions(+), 16 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 413dc73b92..fd63c48a72 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -82,8 +82,26 @@ type Database interface { TrieDB() *triedb.Database } -// Trie is a Ethereum Merkle Patricia trie. type Trie interface { + Itrie + PrefetchAccount(address common.Address) (*types.StateAccount, error) + PrefetchStorage(addr common.Address, key []byte) ([]byte, error) +} + +type LegacyAdapter struct { + Itrie +} + +func (a LegacyAdapter) PrefetchAccount(address common.Address) (*types.StateAccount, error) { + return a.Itrie.GetAccount(address) +} + +func (a LegacyAdapter) PrefetchStorage(addr common.Address, key []byte) ([]byte, error) { + return a.Itrie.GetStorage(addr, key) +} + +// Trie is a Ethereum Merkle Patricia trie. +type Itrie interface { // GetKey returns the sha3 preimage of a hashed key that was previously used // to store a value. // @@ -204,13 +222,14 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { } if db.triedb.IsVerkle() { - return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) + tr, err := trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) + return LegacyAdapter{tr}, err } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { return nil, err } - return tr, nil + return LegacyAdapter{tr}, nil } // OpenStorageTrie opens the storage trie of an account. @@ -240,7 +259,7 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre if err != nil { return nil, err } - return tr, nil + return LegacyAdapter{tr}, nil } // CopyTrie returns an independent copy of the given trie. @@ -248,8 +267,8 @@ func (db *cachingDB) CopyTrie(t Trie) Trie { switch t := t.(type) { case *shim.StateTrie: return t.Copy() - case *trie.StateTrie: - return t.Copy() + case LegacyAdapter: + return LegacyAdapter{t.Itrie.(*trie.StateTrie).Copy()} default: panic(fmt.Errorf("unknown trie type %T", t)) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 3d88f391d3..d503d15bbb 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -212,12 +212,10 @@ func (s *StateDB) StartPrefetcher(namespace string, maxConcurrency int) { if maxConcurrency == 0 { return // No prefetching } - if s.snap != nil { - s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) - kvConfig := s.db.TrieDB().Config().KeyValueDB - if kvConfig != nil && kvConfig.KVBackend != nil { - s.prefetcher.rootTrie = s.trie - } + s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, maxConcurrency) + kvConfig := s.db.TrieDB().Config().KeyValueDB + if kvConfig != nil && kvConfig.KVBackend != nil { + s.prefetcher.rootTrie = s.trie } } diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index b96db7a4dd..f1f72d1eb4 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -564,9 +564,9 @@ func (to *trieOrchestrator) processTasks() { // Perform task var err error if len(fTask) == common.AddressLength { - _, err = t.GetAccount(common.BytesToAddress(fTask)) + _, err = t.PrefetchAccount(common.BytesToAddress(fTask)) } else { - _, err = t.GetStorage(to.sf.addr, fTask) + _, err = t.PrefetchStorage(to.sf.addr, fTask) } if err != nil { log.Error("Trie prefetcher failed fetching", "root", to.sf.root, "err", err) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3b72aeae33..f53bd1f6e3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -717,7 +717,7 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st if err != nil { return nil, err } - storageTrie = st + storageTrie = state.LegacyAdapter{Itrie: st} } // Create the proofs for the storageKeys. for i, key := range keys { diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 1fb4ed1a79..5d78b35ce1 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -31,7 +31,8 @@ func NewLegacyBackend( return &LegacyBackend{tr: tr}, nil } -func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } +func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } +func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } func (b *LegacyBackend) Hash(batch Batch) common.Hash { if b.hashed { diff --git a/shim/kv_backend.go b/shim/kv_backend.go index 82bb1ee3a1..8e2a3fb463 100644 --- a/shim/kv_backend.go +++ b/shim/kv_backend.go @@ -33,6 +33,11 @@ func (k *KVTrieBackend) Get(key []byte) ([]byte, error) { return k.backend.Get(key) } +func (k *KVTrieBackend) Prefetch(key []byte) ([]byte, error) { + fmt.Printf("Prefetch: %x\n", key) + return k.backend.Get(key) +} + func (k *KVTrieBackend) Hash(batch Batch) common.Hash { if k.hashed { return k.hash diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index 50062a98dc..d0f600b175 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -32,6 +32,10 @@ func (m *MerkleDB) Get(key []byte) ([]byte, error) { return val, err } +func (m *MerkleDB) Prefetch(key []byte) ([]byte, error) { + return nil, m.db.PrefetchPath(key) +} + func (m *MerkleDB) latestView() merkledb.Trie { m.lock.RLock() defer m.lock.RUnlock() diff --git a/shim/secure_trie.go b/shim/secure_trie.go index 571be9564f..993905b3c8 100644 --- a/shim/secure_trie.go +++ b/shim/secure_trie.go @@ -67,6 +67,11 @@ func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { return content, err } +func (t *StateTrie) PrefetchStorage(_ common.Address, key []byte) ([]byte, error) { + _, err := t.trie.Prefetch(t.hashKey(key)) + return nil, err +} + // GetAccount attempts to retrieve an account with provided account address. // If the specified account is not in the trie, nil will be returned. // If a trie node is not found in the database, a MissingNodeError is returned. @@ -80,6 +85,11 @@ func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, err return ret, err } +func (t *StateTrie) PrefetchAccount(address common.Address) (*types.StateAccount, error) { + _, err := t.trie.Prefetch(t.hashKey(address.Bytes())) + return nil, err +} + // GetAccountByHash does the same thing as GetAccount, however it expects an // account hash that is the hash of address. This constitutes an abstraction // leak, since the client code needs to know the key format. diff --git a/shim/trie.go b/shim/trie.go index eed6417e54..a99cadd338 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -23,6 +23,7 @@ type Backend interface { Get(key []byte) ([]byte, error) Hash(batch Batch) common.Hash Commit(batch Batch, collectLeaf bool) (common.Hash, *trienode.NodeSet, error) + Prefetch(key []byte) ([]byte, error) // Note []byte value is ignored } func NewStateTrie(backend Backend, db database.Database) *StateTrie { @@ -79,6 +80,11 @@ func (t *Trie) Get(key []byte) ([]byte, error) { return t.backend.Get(key) } +func (t *Trie) Prefetch(key []byte) ([]byte, error) { + key = t.getKey(key) + return t.backend.Prefetch(key) +} + func (t *Trie) Copy() *Trie { if legacy, ok := t.backend.(*LegacyBackend); ok { return &Trie{ diff --git a/triedb/database.go b/triedb/database.go index 7f18e7eaa4..ecfe5d8954 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -46,6 +46,10 @@ type KVBackend interface { // If the key does not exist, it must return (nil, nil). Get(key []byte) ([]byte, error) + // Prefetch loads the intermediary nodes of the given key into memory. + // The first return value is ignored. + Prefetch(key []byte) ([]byte, error) + // After this call, Root() should return the same hash as returned by this call. // Note when len(Value) == 0, it means the key should be deleted. Update(Batch) (common.Hash, error) @@ -55,6 +59,7 @@ type KVBackend interface { // commits happen on a rolling basis. Commit(root common.Hash) error + // Close closes the backend and releases all held resources. Close() error } From 003ac7da5ed4db459af636858202f69aa1f49b95 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 19 Dec 2024 17:09:51 -0800 Subject: [PATCH 087/307] add usePersistedStartBlock --- plugin/evm/reprocess_test.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index f16f7afcf7..cde08ef677 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -32,15 +32,16 @@ var ( ) var ( - sourceDbDir = "sourceDb" - sourcePrefix = "" - dbDir = "" - startBlock = uint64(0) - endBlock = uint64(200) - prefetchers = 4 - useSnapshot = true - pruning = true - skipUpgradeCheck = false + sourceDbDir = "sourceDb" + sourcePrefix = "" + dbDir = "" + startBlock = uint64(0) + endBlock = uint64(200) + prefetchers = 4 + useSnapshot = true + pruning = true + skipUpgradeCheck = false + usePersistedStartBlock = false // merkledb options merkleDBBranchFactor = 16 @@ -60,6 +61,7 @@ func TestMain(m *testing.M) { flag.BoolVar(&useSnapshot, "useSnapshot", useSnapshot, "use snapshot") flag.BoolVar(&pruning, "pruning", pruning, "pruning") flag.BoolVar(&skipUpgradeCheck, "skipUpgradeCheck", skipUpgradeCheck, "skip upgrade check") + flag.BoolVar(&usePersistedStartBlock, "usePersistedStartBlock", usePersistedStartBlock, "use persisted start block") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -265,6 +267,9 @@ func TestReprocessMainnetBlocks(t *testing.T) { lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) + if usePersistedStartBlock { + startBlock = lastHeight + } require.Equal(t, lastHeight, startBlock, "Last height does not match start block") if lastHash != (common.Hash{}) { // Other than when genesis is not performed, start processing from the next block From 436cae0fff413a875e7f91b6293fbfc2e6d90bd7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 16:41:03 -0800 Subject: [PATCH 088/307] add tapeRecorder --- core/blockchain.go | 15 +++- core/state/database.go | 4 +- core/state_prefetcher.go | 36 +++++++--- plugin/evm/reprocess_recording_test.go | 94 ++++++++++++++++++++++++++ plugin/evm/reprocess_test.go | 16 +++++ shim/geth_backend.go | 20 +++++- triedb/database.go | 5 ++ 7 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 plugin/evm/reprocess_recording_test.go diff --git a/core/blockchain.go b/core/blockchain.go index 59d337d590..f59e3962ce 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -347,6 +347,13 @@ type BlockChain struct { // [txIndexTailLock] is used to synchronize the updating of the tx index tail. txIndexTailLock sync.Mutex + + // for logging kvs read from snapshot + snapWriter writer +} + +func (bc *BlockChain) SetSnapWriter(w writer) { + bc.snapWriter = w } type Opts struct { @@ -1388,9 +1395,11 @@ func (bc *BlockChain) insertBlock(block *types.Block, parent *types.Header, writ var statedb *state.StateDB if bc.snaps != nil { snap := bc.snaps.Snapshot(parent.Root) - // withReplay := &snapReplay{snap, *tape} - withReplay := snap - statedb, err = state.NewWithSnapshot(parent.Root, bc.stateCache, withReplay) + withRecording := snap + if bc.snapWriter != nil { + withRecording = &snapRecorder{snap, bc.snapWriter} + } + statedb, err = state.NewWithSnapshot(parent.Root, bc.stateCache, withRecording) } else { statedb, err = state.New(parent.Root, bc.stateCache, nil) } diff --git a/core/state/database.go b/core/state/database.go index fd63c48a72..8ce59c169e 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -214,7 +214,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { } // Legacy backend maintains hash compatibility with geth // to test the shim layer. - backend, err := shim.NewLegacyBackend(root, common.Hash{}, root, db.triedb) + backend, err := shim.NewLegacyBackend(root, common.Hash{}, root, db.triedb, kvConfig.Writer) if err != nil { return nil, err } @@ -243,7 +243,7 @@ func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre } // Legacy backend maintains hash compatibility with geth // to test the shim layer. - backend, err := shim.NewLegacyBackend(stateRoot, addrHash, root, db.triedb) + backend, err := shim.NewLegacyBackend(stateRoot, addrHash, root, db.triedb, kvConfig.Writer) if err != nil { return nil, err } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 8076d19497..cafae15cd5 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -52,9 +52,21 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse // only goal is to pre-cache transaction signatures and state trie nodes. type tape []byte +func (t *tape) RecordAccountRead(_ common.Hash, val []byte) error { + *t = append(*t, byte(len(val))) + *t = append(*t, val...) + return nil +} + +func (t *tape) RecordStorageRead(_, _ common.Hash, val []byte) error { + *t = append(*t, byte(len(val))) + *t = append(*t, val...) + return nil +} + func (t tape) Len() int { return len(t) } -func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, cfg vm.Config, tape *tape) { +func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, cfg vm.Config, recorded *tape) { if p.bc.snaps == nil { log.Warn("Skipping prefetching transactions without snapshot cache") return @@ -137,7 +149,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c log.Error("Failed to finalize block", "err", err) } - *tape = recorder.tape + *recorded = *(recorder.writer.(*tape)) } // precacheTransaction attempts to apply a transaction to the given state database @@ -164,10 +176,14 @@ type vmStateDB interface { GetLogs(common.Hash, uint64, common.Hash) []*types.Log } +type writer interface { + RecordStorageRead(common.Hash, common.Hash, []byte) error + RecordAccountRead(common.Hash, []byte) error +} + type snapRecorder struct { snapshot.Snapshot - - tape tape + writer writer } type snapReplay struct { @@ -183,8 +199,8 @@ func (s *snapRecorder) Account(accHash common.Hash) (*types.SlimAccount, error) } if acc == nil { // fmt.Println("nil account added") - s.tape = append(s.tape, 0) - return nil, nil + err := s.writer.RecordAccountRead(accHash, nil) + return nil, err } rlp, err := rlp.EncodeToBytes(acc) @@ -192,8 +208,7 @@ func (s *snapRecorder) Account(accHash common.Hash) (*types.SlimAccount, error) return nil, err } // fmt.Println("account added", len(rlp)) - s.tape = append(s.tape, byte(len(rlp))) - s.tape = append(s.tape, rlp...) + err = s.writer.RecordAccountRead(accHash, rlp) return acc, err } @@ -203,9 +218,8 @@ func (s *snapRecorder) Storage(accHash common.Hash, hash common.Hash) ([]byte, e return nil, err } // fmt.Println("storage added", len(val)) - s.tape = append(s.tape, byte(len(val))) - s.tape = append(s.tape, val...) - return val, nil + err = s.writer.RecordStorageRead(accHash, hash, val) + return val, err } func (s *snapReplay) Account(accHash common.Hash) (*types.SlimAccount, error) { diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go new file mode 100644 index 0000000000..4223012901 --- /dev/null +++ b/plugin/evm/reprocess_recording_test.go @@ -0,0 +1,94 @@ +package evm + +import ( + "fmt" + + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" +) + +// Each 1000 blocks, we will make a new file. +// Each file contains: +// - Block number (8 bytes) +// - Block hash (32 bytes) +// - Transactions (uint16) +// - Accounts Read (uint16) +// - Storages Read (uint16) +// - Accounts Written (uint16) +// - Storages Written (uint16) +// For each account read: +// - Account address hash (32 bytes) +// - Value len (byte) +// - Value (variable) +// For each storage read: +// - Account address hash (32 bytes) +// - Key hash (32 bytes) +// - Value len (byte) +// - Value (variable) +// For each account written: +// - Account address hash (32 bytes) +// - Value len (byte) +// - Value (variable) +// For each storage written: +// - Account address hash (32 bytes) +// - Key hash (32 bytes) +// - Value len (byte) + +type blockRecorder struct { + accountReads []triedb.KV + accountWrites []triedb.KV + storageReads []triedb.KV + storageWrites []triedb.KV +} + +func (b *blockRecorder) MustUpdate(key, value []byte) { + switch len(key) { + case 32: + b.accountWrites = append(b.accountWrites, triedb.KV{Key: key, Value: value}) + case 64: + b.storageWrites = append(b.storageWrites, triedb.KV{Key: key, Value: value}) + default: + panic("unexpected key length") + } +} + +func (b *blockRecorder) RecordAccountRead(key common.Hash, value []byte) error { + b.accountReads = append(b.accountReads, triedb.KV{Key: key[:], Value: value}) + return nil +} + +func (b *blockRecorder) RecordStorageRead(account common.Hash, key common.Hash, value []byte) error { + b.storageReads = append(b.storageReads, triedb.KV{Key: append(account[:], key[:]...), Value: value}) + return nil +} + +func (b *blockRecorder) Summary(block *types.Block) { + fmt.Printf("Block %d: %s (%d txs) \n", block.NumberU64(), block.Hash().TerminalString(), len(block.Transactions())) + + fmt.Printf("Account Reads: %d\n", len(b.accountReads)) + for _, kv := range b.accountReads { + fmt.Printf(" %x: %x\n", kv.Key, kv.Value) + } + fmt.Printf("Storage Reads: %d\n", len(b.storageReads)) + for _, kv := range b.storageReads { + fmt.Printf(" %x: %x\n", kv.Key, kv.Value) + } + + fmt.Printf("Account Writes: %d\n", len(b.accountWrites)) + for _, kv := range b.accountWrites { + fmt.Printf(" %x: %x\n", kv.Key, kv.Value) + } + + fmt.Printf("Storage Writes: %d\n", len(b.storageWrites)) + for _, kv := range b.storageWrites { + fmt.Printf(" %x: %x\n", kv.Key, kv.Value) + } +} + +func (b *blockRecorder) Reset() { + b.accountReads = nil + b.accountWrites = nil + b.storageReads = nil + b.storageWrites = nil +} diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index cde08ef677..01e7d48f0e 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -42,6 +42,7 @@ var ( pruning = true skipUpgradeCheck = false usePersistedStartBlock = false + tapeDir = "" // merkledb options merkleDBBranchFactor = 16 @@ -62,6 +63,7 @@ func TestMain(m *testing.M) { flag.BoolVar(&pruning, "pruning", pruning, "pruning") flag.BoolVar(&skipUpgradeCheck, "skipUpgradeCheck", skipUpgradeCheck, "skip upgrade check") flag.BoolVar(&usePersistedStartBlock, "usePersistedStartBlock", usePersistedStartBlock, "use persisted start block") + flag.StringVar(&tapeDir, "tapeDir", tapeDir, "directory to store tape") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -332,6 +334,12 @@ func reprocess( return true } + var tapeRecorder *blockRecorder + if tapeDir != "" { + tapeRecorder = &blockRecorder{} + cacheConfig.KeyValueDB.Writer = tapeRecorder + } + var opts []core.Opts cacheConfig.SnapshotDelayInit = true if start > 0 { @@ -370,6 +378,9 @@ func reprocess( bc.Validator().(*core.BlockValidator).CheckRoot = checkRootFn bc.InitializeSnapshots(&core.Opts{LastAcceptedRoot: lastRoot}) + if tapeRecorder != nil { + bc.SetSnapWriter(tapeRecorder) + } for i := start; i <= stop; i++ { block := backend.GetBlock(i) @@ -387,6 +398,11 @@ func reprocess( err = bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) + if tapeRecorder != nil { + tapeRecorder.Summary(block) + tapeRecorder.Reset() + } + t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) errorOnClosed := true // make sure block is accepted err = bc.AcceptWithRoot(block, lastInsertedRoot, errorOnClosed) diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 5d78b35ce1..08e433d617 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -13,10 +13,18 @@ type LegacyBackend struct { hash common.Hash hashed bool tr *trie.Trie + + addrHash common.Hash + writer writer +} + +type writer interface { + MustUpdate(key, value []byte) } func NewLegacyBackend( stateRoot common.Hash, addrHash common.Hash, root common.Hash, db database.Database, + writer writer, ) (*LegacyBackend, error) { trieID := trie.StateTrieID(root) if addrHash != (common.Hash{}) { @@ -28,7 +36,7 @@ func NewLegacyBackend( return nil, err } - return &LegacyBackend{tr: tr}, nil + return &LegacyBackend{tr: tr, addrHash: addrHash, writer: writer}, nil } func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } @@ -40,6 +48,16 @@ func (b *LegacyBackend) Hash(batch Batch) common.Hash { } for _, kv := range batch { b.tr.MustUpdate(kv.Key, kv.Value) + + if b.writer == nil { + continue + } + if b.addrHash != (common.Hash{}) { + key := append(b.addrHash.Bytes(), kv.Key...) + b.writer.MustUpdate(key, kv.Value) + } else { + b.writer.MustUpdate(kv.Key, kv.Value) + } } b.hashed = true b.hash = b.tr.Hash() diff --git a/triedb/database.go b/triedb/database.go index ecfe5d8954..ffc2d64bcc 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -63,8 +63,13 @@ type KVBackend interface { Close() error } +type KVWriter interface { + MustUpdate(key, value []byte) +} + type KeyValueConfig struct { KVBackend KVBackend + Writer KVWriter } // Config defines all necessary options for database. From e414e580491329dbcdde84d4175acc1fd807ac72 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 16:50:50 -0800 Subject: [PATCH 089/307] better logging w/ tape --- plugin/evm/reprocess_recording_test.go | 17 +++++++++++++++-- plugin/evm/reprocess_test.go | 8 +++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 4223012901..0c6d029c00 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -13,6 +13,7 @@ import ( // - Block number (8 bytes) // - Block hash (32 bytes) // - Transactions (uint16) +// - Atomic transactions (uint16) // - Accounts Read (uint16) // - Storages Read (uint16) // - Accounts Written (uint16) @@ -63,9 +64,21 @@ func (b *blockRecorder) RecordStorageRead(account common.Hash, key common.Hash, return nil } -func (b *blockRecorder) Summary(block *types.Block) { - fmt.Printf("Block %d: %s (%d txs) \n", block.NumberU64(), block.Hash().TerminalString(), len(block.Transactions())) +func (b *blockRecorder) Summary(block *types.Block, atomicTxs uint16) { + fmt.Printf("Block %d: %s (%d txs + %d atomic)\tReads: %d(a) %d(s), Writes: %d(a) %d(s)\n", + block.NumberU64(), + block.Hash().TerminalString(), + len(block.Transactions()), + atomicTxs, + len(b.accountReads), + len(b.storageReads), + len(b.accountWrites), + len(b.storageWrites), + ) + if !tapeVerbose { + return + } fmt.Printf("Account Reads: %d\n", len(b.accountReads)) for _, kv := range b.accountReads { fmt.Printf(" %x: %x\n", kv.Key, kv.Value) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 01e7d48f0e..c6e653d8f7 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -43,6 +43,7 @@ var ( skipUpgradeCheck = false usePersistedStartBlock = false tapeDir = "" + tapeVerbose = false // merkledb options merkleDBBranchFactor = 16 @@ -64,6 +65,7 @@ func TestMain(m *testing.M) { flag.BoolVar(&skipUpgradeCheck, "skipUpgradeCheck", skipUpgradeCheck, "skip upgrade check") flag.BoolVar(&usePersistedStartBlock, "usePersistedStartBlock", usePersistedStartBlock, "use persisted start block") flag.StringVar(&tapeDir, "tapeDir", tapeDir, "directory to store tape") + flag.BoolVar(&tapeVerbose, "tapeVerbose", tapeVerbose, "verbose tape") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -326,7 +328,6 @@ func reprocess( var lastInsertedRoot common.Hash checkRootFn := func(expected, got common.Hash) bool { - t.Logf("Got root: %x (original: %x)", got, expected) lastInsertedRoot = got if backend.VerifyRoot { return expected == got @@ -387,7 +388,6 @@ func reprocess( isApricotPhase5 := backend.Genesis.Config.IsApricotPhase5(block.Time()) atomicTxs, err := ExtractAtomicTxs(block.ExtData(), isApricotPhase5, Codec) require.NoError(t, err) - t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) // Override parentRoot to match last state parent := bc.GetHeaderByNumber(block.NumberU64() - 1) @@ -399,8 +399,10 @@ func reprocess( require.NoError(t, err) if tapeRecorder != nil { - tapeRecorder.Summary(block) + tapeRecorder.Summary(block, uint16(len(atomicTxs))) tapeRecorder.Reset() + } else { + t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) } t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) From fca800de876d0dd878db77b33737bea0be3337ce Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 16:53:16 -0800 Subject: [PATCH 090/307] reduce logs --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c6e653d8f7..ab0becf716 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -405,7 +405,7 @@ func reprocess( t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) } - t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) + // t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) errorOnClosed := true // make sure block is accepted err = bc.AcceptWithRoot(block, lastInsertedRoot, errorOnClosed) require.NoError(t, err) From 02e97dbdfccf56d6eda3f9b174a5e1b470f285f2 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 16:54:27 -0800 Subject: [PATCH 091/307] formatting --- plugin/evm/reprocess_recording_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 0c6d029c00..d188ed7953 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -65,7 +65,7 @@ func (b *blockRecorder) RecordStorageRead(account common.Hash, key common.Hash, } func (b *blockRecorder) Summary(block *types.Block, atomicTxs uint16) { - fmt.Printf("Block %d: %s (%d txs + %d atomic)\tReads: %d(a) %d(s), Writes: %d(a) %d(s)\n", + fmt.Printf("Block %d: %s (%d txs + %d atomic)\tReads (acc, storage): %d, %d\t Writes: %d, %d\n", block.NumberU64(), block.Hash().TerminalString(), len(block.Transactions()), From 9a4a636db8a9016076625a61cc899fd01c6cea71 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 17:56:39 -0800 Subject: [PATCH 092/307] fixes --- core/blockchain.go | 6 +++++- core/state_manager.go | 6 ++++++ core/state_prefetcher.go | 5 ++--- plugin/evm/reprocess_backend_test.go | 1 + plugin/evm/reprocess_recording_test.go | 2 +- plugin/evm/reprocess_test.go | 2 ++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index f59e3962ce..84ecc02d63 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -426,7 +426,11 @@ func NewBlockChain( bc.currentBlock.Store(nil) // Create the state manager - bc.stateManager = NewTrieWriter(bc.triedb, cacheConfig) + var tdb TrieDB = bc.triedb + if cacheConfig.StateScheme == rawdb.PathScheme { + tdb = &NoDerefTrieDB{tdb} + } + bc.stateManager = NewTrieWriter(tdb, cacheConfig) // if err := bc.reprocessFromGenesis(); err != nil { // return nil, err diff --git a/core/state_manager.go b/core/state_manager.go index 312eab4751..2d2360c543 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -71,6 +71,12 @@ type TrieDB interface { Cap(limit common.StorageSize) error } +type NoDerefTrieDB struct { + TrieDB +} + +func (nd *NoDerefTrieDB) Dereference(root common.Hash) error { return nil } + func NewTrieWriter(db TrieDB, config *CacheConfig) TrieWriter { if config.Pruning { cm := &cappedMemoryTrieWriter{ diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index cafae15cd5..e641405bda 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -83,7 +83,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c blockContext = NewEVMBlockContext(header, p.bc, nil) signer = types.MakeSigner(p.config, header.Number, header.Time) ) - recorder := &snapRecorder{Snapshot: snap} + recorder := &snapRecorder{Snapshot: snap, writer: recorded} statedb, err := state.NewWithSnapshot(parentRoot, p.bc.stateCache, recorder) if err != nil { return @@ -148,8 +148,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, c if err := p.engine.Finalize(p.bc, block, parent, statedb, receipts); err != nil { log.Error("Failed to finalize block", "err", err) } - - *recorded = *(recorder.writer.(*tape)) + *recorded = *recorder.writer.(*tape) } // precacheTransaction attempts to apply a transaction to the given state database diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index cc79a2ff0c..0d883f13f0 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -65,6 +65,7 @@ func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB func getCacheConfig(t *testing.T, name string, backend triedb.KVBackend) core.CacheConfig { cacheConfig := *core.DefaultCacheConfig + cacheConfig.StateScheme = legacyScheme cacheConfig.KeyValueDB = &triedb.KeyValueConfig{KVBackend: backend} cacheConfig.TriePrefetcherParallelism = prefetchers cacheConfig.SnapshotLimit = 0 diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index d188ed7953..d93f460986 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -8,7 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// Each 1000 blocks, we will make a new file. +// Each 10000 blocks, we will make a new file. // Each file contains: // - Block number (8 bytes) // - Block hash (32 bytes) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index ab0becf716..61a9239c2d 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -44,6 +44,7 @@ var ( usePersistedStartBlock = false tapeDir = "" tapeVerbose = false + legacyScheme = rawdb.HashScheme // merkledb options merkleDBBranchFactor = 16 @@ -66,6 +67,7 @@ func TestMain(m *testing.M) { flag.BoolVar(&usePersistedStartBlock, "usePersistedStartBlock", usePersistedStartBlock, "use persisted start block") flag.StringVar(&tapeDir, "tapeDir", tapeDir, "directory to store tape") flag.BoolVar(&tapeVerbose, "tapeVerbose", tapeVerbose, "verbose tape") + flag.StringVar(&legacyScheme, "legacyScheme", legacyScheme, "legacy scheme (hash or path)") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") From a21a86ebd3fc2fc00984c968311f28724fad2e8a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 18:37:22 -0800 Subject: [PATCH 093/307] add file manager --- plugin/evm/reprocess_recording_test.go | 143 +++++++++++++++++++++++++ plugin/evm/reprocess_test.go | 9 +- 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index d93f460986..96fad8be99 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -1,7 +1,10 @@ package evm import ( + "encoding/binary" "fmt" + "io" + "os" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/triedb" @@ -41,6 +44,8 @@ type blockRecorder struct { accountWrites []triedb.KV storageReads []triedb.KV storageWrites []triedb.KV + + fileManager *fileManager } func (b *blockRecorder) MustUpdate(key, value []byte) { @@ -64,6 +69,23 @@ func (b *blockRecorder) RecordStorageRead(account common.Hash, key common.Hash, return nil } +func (b *blockRecorder) WriteToDisk(block *types.Block, atomicTxs uint16) { + if b.fileManager == nil { + return + } + w := b.fileManager.GetWriterFor(block.NumberU64()) + if b.Write(block, atomicTxs, w) != nil { + panic("failed to write") + } +} + +func (b *blockRecorder) Close() { + if b.fileManager == nil { + return + } + b.fileManager.Close() +} + func (b *blockRecorder) Summary(block *types.Block, atomicTxs uint16) { fmt.Printf("Block %d: %s (%d txs + %d atomic)\tReads (acc, storage): %d, %d\t Writes: %d, %d\n", block.NumberU64(), @@ -99,9 +121,130 @@ func (b *blockRecorder) Summary(block *types.Block, atomicTxs uint16) { } } +func writeByte(w io.Writer, b byte) error { + _, err := w.Write([]byte{b}) + return err +} + +func writeUint16(w io.Writer, i uint16) error { + _, err := w.Write(binary.BigEndian.AppendUint16(nil, i)) + return err +} + +func writeUint64(w io.Writer, i uint64) error { + _, err := w.Write(binary.BigEndian.AppendUint64(nil, i)) + return err +} + +func (b *blockRecorder) Write(block *types.Block, atomicTxs uint16, w io.Writer) error { + if err := writeUint64(w, block.NumberU64()); err != nil { + return err + } + if _, err := w.Write(block.Hash().Bytes()); err != nil { + return err + } + if err := writeUint16(w, uint16(len(block.Transactions()))); err != nil { + return err + } + if err := writeUint16(w, atomicTxs); err != nil { + return err + } + if err := writeUint16(w, uint16(len(b.accountReads))); err != nil { + return err + } + if err := writeUint16(w, uint16(len(b.storageReads))); err != nil { + return err + } + if err := writeUint16(w, uint16(len(b.accountWrites))); err != nil { + return err + } + if err := writeUint16(w, uint16(len(b.storageWrites))); err != nil { + return err + } + + for _, kv := range b.accountReads { + if _, err := w.Write(kv.Key); err != nil { + return err + } + if err := writeByte(w, byte(len(kv.Value))); err != nil { + return err + } + if _, err := w.Write(kv.Value); err != nil { + return err + } + } + + for _, kv := range b.storageReads { + if _, err := w.Write(kv.Key); err != nil { + return err + } + if err := writeByte(w, byte(len(kv.Value))); err != nil { + return err + } + if _, err := w.Write(kv.Value); err != nil { + return err + } + } + + for _, kv := range b.accountWrites { + if _, err := w.Write(kv.Key); err != nil { + return err + } + if err := writeByte(w, byte(len(kv.Value))); err != nil { + return err + } + if _, err := w.Write(kv.Value); err != nil { + return err + } + } + + for _, kv := range b.storageWrites { + if _, err := w.Write(kv.Key); err != nil { + return err + } + if err := writeByte(w, byte(len(kv.Value))); err != nil { + return err + } + if _, err := w.Write(kv.Value); err != nil { + return err + } + } + return nil +} + func (b *blockRecorder) Reset() { b.accountReads = nil b.accountWrites = nil b.storageReads = nil b.storageWrites = nil } + +type fileManager struct { + dir string + newEach uint64 + lastFile uint64 + f *os.File +} + +func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { + group := blockNumber - blockNumber%f.newEach + if group == f.lastFile && f.f != nil { + return f.f + } + if f.f != nil { + f.f.Close() + } + file, err := os.OpenFile(fmt.Sprintf("%s/%07d", f.dir, group), os.O_APPEND, 0644) + if err != nil { + panic(err) + } + f.f = file + return f.f +} + +func (f *fileManager) Close() error { + if f.f != nil { + return f.f.Close() + } + return nil +} diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 61a9239c2d..ba1922d048 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -339,7 +339,10 @@ func reprocess( var tapeRecorder *blockRecorder if tapeDir != "" { - tapeRecorder = &blockRecorder{} + tapeRecorder = &blockRecorder{ + fileManager: &fileManager{dir: tapeDir}, + } + defer tapeRecorder.Close() cacheConfig.KeyValueDB.Writer = tapeRecorder } @@ -363,6 +366,9 @@ func reprocess( defer lock.Unlock() bc.Stop() + if tapeRecorder != nil { + tapeRecorder.Close() + } }) if start == 0 { @@ -402,6 +408,7 @@ func reprocess( if tapeRecorder != nil { tapeRecorder.Summary(block, uint16(len(atomicTxs))) + tapeRecorder.WriteToDisk(block, uint16(len(atomicTxs))) tapeRecorder.Reset() } else { t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) From 718b2a2d02eced628cd3294a738fa51e5df206c9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 18:40:39 -0800 Subject: [PATCH 094/307] fix --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index ba1922d048..afc45df511 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -340,7 +340,7 @@ func reprocess( var tapeRecorder *blockRecorder if tapeDir != "" { tapeRecorder = &blockRecorder{ - fileManager: &fileManager{dir: tapeDir}, + fileManager: &fileManager{dir: tapeDir, newEach: 10_000}, } defer tapeRecorder.Close() cacheConfig.KeyValueDB.Writer = tapeRecorder From e277e9985afd9f81772f9ea5343971dc2f97dc1c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 18:42:59 -0800 Subject: [PATCH 095/307] add create permissions --- plugin/evm/reprocess_recording_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 96fad8be99..d325ae8e5e 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -234,7 +234,7 @@ func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { if f.f != nil { f.f.Close() } - file, err := os.OpenFile(fmt.Sprintf("%s/%07d", f.dir, group), os.O_APPEND, 0644) + file, err := os.OpenFile(fmt.Sprintf("%s/%07d", f.dir, group), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic(err) } From ee1d9bc54679f9528255dc071f62ec5afe9ab3f4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 18:51:55 -0800 Subject: [PATCH 096/307] align files --- plugin/evm/reprocess_recording_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index d325ae8e5e..59ef708bde 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -234,7 +234,7 @@ func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { if f.f != nil { f.f.Close() } - file, err := os.OpenFile(fmt.Sprintf("%s/%07d", f.dir, group), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + file, err := os.OpenFile(fmt.Sprintf("%s/%08d", f.dir, group), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic(err) } From 5ca3b3680becbac570a410a4a36bfe2d86db6008 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 23 Dec 2024 20:06:50 -0800 Subject: [PATCH 097/307] dont cap on pathdb --- core/state_manager.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state_manager.go b/core/state_manager.go index 2d2360c543..6dde56397c 100644 --- a/core/state_manager.go +++ b/core/state_manager.go @@ -76,6 +76,7 @@ type NoDerefTrieDB struct { } func (nd *NoDerefTrieDB) Dereference(root common.Hash) error { return nil } +func (nd *NoDerefTrieDB) Cap(limit common.StorageSize) error { return nil } func NewTrieWriter(db TrieDB, config *CacheConfig) TrieWriter { if config.Pruning { From 7172a1419293b0f1bcddf093c988fb70e8ce08a1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 24 Dec 2024 07:39:28 -0800 Subject: [PATCH 098/307] add option for trie clean cache --- plugin/evm/reprocess_backend_test.go | 3 +++ plugin/evm/reprocess_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 0d883f13f0..a0810619e2 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -72,6 +72,9 @@ func getCacheConfig(t *testing.T, name string, backend triedb.KVBackend) core.Ca if useSnapshot { cacheConfig.SnapshotLimit = 256 } + if trieCleanCacheMBs > 0 { + cacheConfig.TrieCleanLimit = trieCleanCacheMBs + } cacheConfig.Pruning = pruning return cacheConfig } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index afc45df511..323dcdaecf 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -45,6 +45,7 @@ var ( tapeDir = "" tapeVerbose = false legacyScheme = rawdb.HashScheme + trieCleanCacheMBs = 0 // merkledb options merkleDBBranchFactor = 16 @@ -68,6 +69,7 @@ func TestMain(m *testing.M) { flag.StringVar(&tapeDir, "tapeDir", tapeDir, "directory to store tape") flag.BoolVar(&tapeVerbose, "tapeVerbose", tapeVerbose, "verbose tape") flag.StringVar(&legacyScheme, "legacyScheme", legacyScheme, "legacy scheme (hash or path)") + flag.IntVar(&trieCleanCacheMBs, "trieCleanCacheMBs", trieCleanCacheMBs, "clean cache size in MB") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") From 08d1cef547c5dd641db938c8eb3753095cbb6bc8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 24 Dec 2024 18:47:39 -0800 Subject: [PATCH 099/307] use tape representation --- core/blockchain.go | 6 +- core/state_prefetcher.go | 5 ++ core/state_processor.go | 7 +- core/types.go | 2 +- plugin/evm/reprocess_recording_test.go | 106 ++++++++++++------------- 5 files changed, 69 insertions(+), 57 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 84ecc02d63..59cb8a108c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1420,7 +1420,11 @@ func (bc *BlockChain) insertBlock(block *types.Block, parent *types.Header, writ accountMissStart := snapshot.SnapshotCleanAccountMissMeter.Snapshot().Count() storageMissStart := snapshot.SnapshotCleanStorageMissMeter.Snapshot().Count() pstart := time.Now() - receipts, logs, usedGas, err := bc.processor.Process(block, parent, statedb, bc.vmConfig) + var writers []writer + if bc.snapWriter != nil { + writers = append(writers, bc.snapWriter) + } + receipts, logs, usedGas, err := bc.processor.Process(block, parent, statedb, bc.vmConfig, writers...) if serr := statedb.Error(); serr != nil { log.Error("statedb error encountered", "err", serr, "number", block.Number(), "hash", block.Hash()) } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index e641405bda..c33bbae7a2 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -64,6 +64,10 @@ func (t *tape) RecordStorageRead(_, _ common.Hash, val []byte) error { return nil } +func (t *tape) RecordTransactionEnd() error { + return nil +} + func (t tape) Len() int { return len(t) } func (p *statePrefetcher) Prefetch(block *types.Block, parentRoot common.Hash, cfg vm.Config, recorded *tape) { @@ -178,6 +182,7 @@ type vmStateDB interface { type writer interface { RecordStorageRead(common.Hash, common.Hash, []byte) error RecordAccountRead(common.Hash, []byte) error + RecordTransactionEnd() error } type snapRecorder struct { diff --git a/core/state_processor.go b/core/state_processor.go index efd1c1444c..eab72b0568 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -69,7 +69,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, parent *types.Header, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *StateProcessor) Process(block *types.Block, parent *types.Header, statedb *state.StateDB, cfg vm.Config, notify ...writer) (types.Receipts, []*types.Log, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) @@ -108,6 +108,11 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) + for _, w := range notify { + if err := w.RecordTransactionEnd(); err != nil { + return nil, nil, 0, fmt.Errorf("could not record transaction end: %w", err) + } + } } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) if err := p.engine.Finalize(p.bc, block, parent, statedb, receipts); err != nil { diff --git a/core/types.go b/core/types.go index 77e6dd4d2b..8c6baea6d9 100644 --- a/core/types.go +++ b/core/types.go @@ -49,5 +49,5 @@ type Processor interface { // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. - Process(block *types.Block, parent *types.Header, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) + Process(block *types.Block, parent *types.Header, statedb *state.StateDB, cfg vm.Config, notify ...writer) (types.Receipts, []*types.Log, uint64, error) } diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 59ef708bde..b5524399ad 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -17,19 +17,21 @@ import ( // - Block hash (32 bytes) // - Transactions (uint16) // - Atomic transactions (uint16) -// - Accounts Read (uint16) -// - Storages Read (uint16) +// - Length of read tape (uint32) +// - Read tape (variable) +// - byte type (1 byte) +// - type = account +// - Account address hash (32 bytes) +// - Value len (byte) +// - Value (variable) +// - type = storage +// - Account address hash (32 bytes) +// - Key hash (32 bytes) +// - Value len (byte) +// - Value (variable) +// - type = end tx // - Accounts Written (uint16) // - Storages Written (uint16) -// For each account read: -// - Account address hash (32 bytes) -// - Value len (byte) -// - Value (variable) -// For each storage read: -// - Account address hash (32 bytes) -// - Key hash (32 bytes) -// - Value len (byte) -// - Value (variable) // For each account written: // - Account address hash (32 bytes) // - Value len (byte) @@ -40,9 +42,11 @@ import ( // - Value len (byte) type blockRecorder struct { - accountReads []triedb.KV + accountReads int + storageReads int + readTape []byte + accountWrites []triedb.KV - storageReads []triedb.KV storageWrites []triedb.KV fileManager *fileManager @@ -59,13 +63,31 @@ func (b *blockRecorder) MustUpdate(key, value []byte) { } } +const typeAccount = 0 +const typeStorage = 1 +const typeEndTx = 2 + func (b *blockRecorder) RecordAccountRead(key common.Hash, value []byte) error { - b.accountReads = append(b.accountReads, triedb.KV{Key: key[:], Value: value}) + b.accountReads++ + b.readTape = append(b.readTape, typeAccount) + b.readTape = append(b.readTape, key[:]...) + b.readTape = append(b.readTape, byte(len(value))) + b.readTape = append(b.readTape, value...) return nil } func (b *blockRecorder) RecordStorageRead(account common.Hash, key common.Hash, value []byte) error { - b.storageReads = append(b.storageReads, triedb.KV{Key: append(account[:], key[:]...), Value: value}) + b.storageReads++ + b.readTape = append(b.readTape, typeStorage) + b.readTape = append(b.readTape, account[:]...) + b.readTape = append(b.readTape, key[:]...) + b.readTape = append(b.readTape, byte(len(value))) + b.readTape = append(b.readTape, value...) + return nil +} + +func (b *blockRecorder) RecordTransactionEnd() error { + b.readTape = append(b.readTape, typeEndTx) return nil } @@ -87,13 +109,14 @@ func (b *blockRecorder) Close() { } func (b *blockRecorder) Summary(block *types.Block, atomicTxs uint16) { - fmt.Printf("Block %d: %s (%d txs + %d atomic)\tReads (acc, storage): %d, %d\t Writes: %d, %d\n", + fmt.Printf("Block %d: %s (%d txs + %d atomic)\tReads (acc, storage, tape KBs): %d, %d, %d\t Writes: %d, %d\n", block.NumberU64(), block.Hash().TerminalString(), len(block.Transactions()), atomicTxs, - len(b.accountReads), - len(b.storageReads), + b.accountReads, + b.storageReads, + len(b.readTape)/1024, len(b.accountWrites), len(b.storageWrites), ) @@ -101,14 +124,7 @@ func (b *blockRecorder) Summary(block *types.Block, atomicTxs uint16) { if !tapeVerbose { return } - fmt.Printf("Account Reads: %d\n", len(b.accountReads)) - for _, kv := range b.accountReads { - fmt.Printf(" %x: %x\n", kv.Key, kv.Value) - } - fmt.Printf("Storage Reads: %d\n", len(b.storageReads)) - for _, kv := range b.storageReads { - fmt.Printf(" %x: %x\n", kv.Key, kv.Value) - } + fmt.Printf("Read Tape: %x\n", b.readTape) fmt.Printf("Account Writes: %d\n", len(b.accountWrites)) for _, kv := range b.accountWrites { @@ -131,6 +147,11 @@ func writeUint16(w io.Writer, i uint16) error { return err } +func writeUint32(w io.Writer, i uint32) error { + _, err := w.Write(binary.BigEndian.AppendUint32(nil, i)) + return err +} + func writeUint64(w io.Writer, i uint64) error { _, err := w.Write(binary.BigEndian.AppendUint64(nil, i)) return err @@ -149,10 +170,10 @@ func (b *blockRecorder) Write(block *types.Block, atomicTxs uint16, w io.Writer) if err := writeUint16(w, atomicTxs); err != nil { return err } - if err := writeUint16(w, uint16(len(b.accountReads))); err != nil { + if err := writeUint32(w, uint32(len(b.readTape))); err != nil { return err } - if err := writeUint16(w, uint16(len(b.storageReads))); err != nil { + if _, err := w.Write(b.readTape); err != nil { return err } if err := writeUint16(w, uint16(len(b.accountWrites))); err != nil { @@ -162,30 +183,6 @@ func (b *blockRecorder) Write(block *types.Block, atomicTxs uint16, w io.Writer) return err } - for _, kv := range b.accountReads { - if _, err := w.Write(kv.Key); err != nil { - return err - } - if err := writeByte(w, byte(len(kv.Value))); err != nil { - return err - } - if _, err := w.Write(kv.Value); err != nil { - return err - } - } - - for _, kv := range b.storageReads { - if _, err := w.Write(kv.Key); err != nil { - return err - } - if err := writeByte(w, byte(len(kv.Value))); err != nil { - return err - } - if _, err := w.Write(kv.Value); err != nil { - return err - } - } - for _, kv := range b.accountWrites { if _, err := w.Write(kv.Key); err != nil { return err @@ -213,10 +210,11 @@ func (b *blockRecorder) Write(block *types.Block, atomicTxs uint16, w io.Writer) } func (b *blockRecorder) Reset() { - b.accountReads = nil + b.readTape = nil b.accountWrites = nil - b.storageReads = nil b.storageWrites = nil + b.storageReads = 0 + b.accountReads = 0 } type fileManager struct { From a0bb20def394e9c22b817c73b73d4a88a670c2e9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 24 Dec 2024 18:54:35 -0800 Subject: [PATCH 100/307] add verification for tx end markers --- plugin/evm/reprocess_recording_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index b5524399ad..4426f6a9a8 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -44,6 +44,7 @@ import ( type blockRecorder struct { accountReads int storageReads int + txEnds int readTape []byte accountWrites []triedb.KV @@ -87,6 +88,7 @@ func (b *blockRecorder) RecordStorageRead(account common.Hash, key common.Hash, } func (b *blockRecorder) RecordTransactionEnd() error { + b.txEnds++ b.readTape = append(b.readTape, typeEndTx) return nil } @@ -158,6 +160,9 @@ func writeUint64(w io.Writer, i uint64) error { } func (b *blockRecorder) Write(block *types.Block, atomicTxs uint16, w io.Writer) error { + if len(block.Transactions()) != b.txEnds { + panic(fmt.Sprintf("mismatch block txs: %d, ends recorded: %d", len(block.Transactions()), b.txEnds)) + } if err := writeUint64(w, block.NumberU64()); err != nil { return err } @@ -215,6 +220,7 @@ func (b *blockRecorder) Reset() { b.storageWrites = nil b.storageReads = 0 b.accountReads = 0 + b.txEnds = 0 } type fileManager struct { From 52d9c3deb129524d9c02c8f8b179324b168a116d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 18:59:11 -0800 Subject: [PATCH 101/307] some postprocessing --- plugin/evm/post_processing_test.go | 109 +++++++++++++++++++++++++ plugin/evm/reprocess_recording_test.go | 61 ++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 plugin/evm/post_processing_test.go diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go new file mode 100644 index 0000000000..480e405ed7 --- /dev/null +++ b/plugin/evm/post_processing_test.go @@ -0,0 +1,109 @@ +package evm + +import ( + "io" + "testing" + + "github.com/stretchr/testify/require" +) + +type totals struct { + blocks uint64 + txs uint64 + atomicTxs uint64 + accountReads uint64 + storageReads uint64 + accountWrites uint64 + storageWrites uint64 +} + +func TestPostProcess(t *testing.T) { + start, end := startBlock, endBlock + if start == 0 { + start = 1 // TODO: Verify whether genesis outs were recorded in the first block + } + + var totals totals + fm := &fileManager{dir: tapeDir, newEach: 10_000} + for i := start; i <= end; i++ { + r := fm.GetReaderFor(i) + + blockNumber, err := readUint64(r) + require.NoError(t, err) + require.Equal(t, i, blockNumber) + + blockHash, err := readHash(r) + require.NoError(t, err) + + txs, err := readUint16(r) + require.NoError(t, err) + + atomicTxs, err := readUint16(r) + require.NoError(t, err) + + tapeTxs, accountReads, storageReads := processTape(t, r) + require.Equal(t, txs, tapeTxs) + + accountWrites, err := readUint16(r) + require.NoError(t, err) + + storageWrites, err := readUint16(r) + require.NoError(t, err) + + for j := 0; j < int(accountWrites); j++ { + k, v, err := readKV(r, 32) + require.NoError(t, err) + if tapeVerbose { + t.Logf("account write: %x -> %x", k, v) + } + } + for j := 0; j < int(storageWrites); j++ { + k, v, err := readKV(r, 64) + require.NoError(t, err) + if tapeVerbose { + t.Logf("storage write: %x -> %x", k, v) + } + } + + totals.blocks++ + totals.txs += uint64(txs) + totals.atomicTxs += uint64(atomicTxs) + totals.accountReads += uint64(accountReads) + totals.storageReads += uint64(storageReads) + totals.accountWrites += uint64(accountWrites) + totals.storageWrites += uint64(storageWrites) + + t.Logf("Block[%d:%s]: totals: (txs: %d, atomic: %d, accReads: %d, storageReads: %d, accWrites: %d, storageWrites: %d)", + blockNumber, blockHash, txs, atomicTxs, accountReads, storageReads, accountWrites, storageWrites) + } +} + +func processTape(t *testing.T, r io.Reader) (uint16, int, int) { + length, err := readUint32(r) + require.NoError(t, err) + + pos := 0 + txCount := uint16(0) + accountReads, storageReads := 0, 0 + for pos < int(length) { + typ, err := readByte(r) + require.NoError(t, err) + pos++ + + switch typ { + case typeAccount: + _, val, err := readKV(r, 32) + require.NoError(t, err) + pos += 32 + 1 + len(val) + accountReads++ + case typeStorage: + _, val, err := readKV(r, 64) + require.NoError(t, err) + pos += 64 + 1 + len(val) + storageReads++ + case typeEndTx: + txCount++ + } + } + return txCount, accountReads, storageReads +} diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 4426f6a9a8..d3dd6c3c35 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -159,6 +159,51 @@ func writeUint64(w io.Writer, i uint64) error { return err } +func readByte(r io.Reader) (byte, error) { + buf := make([]byte, 1) + _, err := r.Read(buf) + return buf[0], err +} + +func readUint16(r io.Reader) (uint16, error) { + buf := make([]byte, 2) + _, err := r.Read(buf) + return binary.BigEndian.Uint16(buf), err +} + +func readUint32(r io.Reader) (uint32, error) { + buf := make([]byte, 4) + _, err := r.Read(buf) + return binary.BigEndian.Uint32(buf), err +} + +func readUint64(r io.Reader) (uint64, error) { + buf := make([]byte, 8) + _, err := r.Read(buf) + return binary.BigEndian.Uint64(buf), err +} + +func readKV(r io.Reader, keyLen int) ([]byte, []byte, error) { + key := make([]byte, keyLen) + _, err := r.Read(key) + if err != nil { + return nil, nil, err + } + valLen, err := readByte(r) + if err != nil { + return nil, nil, err + } + val := make([]byte, valLen) + _, err = r.Read(val) + return key, val, err +} + +func readHash(r io.Reader) (common.Hash, error) { + var h common.Hash + _, err := r.Read(h[:]) + return h, err +} + func (b *blockRecorder) Write(block *types.Block, atomicTxs uint16, w io.Writer) error { if len(block.Transactions()) != b.txEnds { panic(fmt.Sprintf("mismatch block txs: %d, ends recorded: %d", len(block.Transactions()), b.txEnds)) @@ -246,6 +291,22 @@ func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { return f.f } +func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { + group := blockNumber - blockNumber%f.newEach + if group == f.lastFile && f.f != nil { + return f.f + } + if f.f != nil { + f.f.Close() + } + file, err := os.Open(fmt.Sprintf("%s/%08d", f.dir, group)) + if err != nil { + panic(err) + } + f.f = file + return f.f +} + func (f *fileManager) Close() error { if f.f != nil { return f.f.Close() From f704a65f52b7f6789aa4fdf7832e1c1ec78dd06c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 19:03:35 -0800 Subject: [PATCH 102/307] formatting --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 480e405ed7..fac3800365 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -74,7 +74,7 @@ func TestPostProcess(t *testing.T) { totals.storageWrites += uint64(storageWrites) t.Logf("Block[%d:%s]: totals: (txs: %d, atomic: %d, accReads: %d, storageReads: %d, accWrites: %d, storageWrites: %d)", - blockNumber, blockHash, txs, atomicTxs, accountReads, storageReads, accountWrites, storageWrites) + blockNumber, blockHash.TerminalString(), txs, atomicTxs, accountReads, storageReads, accountWrites, storageWrites) } } From f38d9d53d254cc2d2cd66a6b6e0da9e84311a779 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 19:04:43 -0800 Subject: [PATCH 103/307] formatting --- plugin/evm/post_processing_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index fac3800365..f417da46ca 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -74,7 +74,8 @@ func TestPostProcess(t *testing.T) { totals.storageWrites += uint64(storageWrites) t.Logf("Block[%d:%s]: totals: (txs: %d, atomic: %d, accReads: %d, storageReads: %d, accWrites: %d, storageWrites: %d)", - blockNumber, blockHash.TerminalString(), txs, atomicTxs, accountReads, storageReads, accountWrites, storageWrites) + blockNumber, blockHash.TerminalString(), totals.txs, totals.atomicTxs, + totals.accountReads, totals.storageReads, totals.accountWrites, totals.storageWrites) } } From ee5080cef1340ecb2598e8370b18c4a44ba66be6 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 19:16:06 -0800 Subject: [PATCH 104/307] try --- plugin/evm/post_processing_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f417da46ca..fc39f65bfe 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -30,7 +30,10 @@ func TestPostProcess(t *testing.T) { blockNumber, err := readUint64(r) require.NoError(t, err) - require.Equal(t, i, blockNumber) + if i != blockNumber { + t.Logf("blockNumber: %d (expected: %d)", blockNumber, i) + } + //require.Equal(t, i, blockNumber) blockHash, err := readHash(r) require.NoError(t, err) From 48e37e088da14f05001c707737cfd2599675e070 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 19:27:35 -0800 Subject: [PATCH 105/307] update lastfile --- plugin/evm/reprocess_recording_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index d3dd6c3c35..734026ec0a 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -288,6 +288,7 @@ func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { panic(err) } f.f = file + f.lastFile = group return f.f } @@ -304,6 +305,7 @@ func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { panic(err) } f.f = file + f.lastFile = group return f.f } From 0f870d9c2e25e628ae131a646364ba19f6801050 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 19:30:16 -0800 Subject: [PATCH 106/307] require again --- plugin/evm/post_processing_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index fc39f65bfe..f417da46ca 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -30,10 +30,7 @@ func TestPostProcess(t *testing.T) { blockNumber, err := readUint64(r) require.NoError(t, err) - if i != blockNumber { - t.Logf("blockNumber: %d (expected: %d)", blockNumber, i) - } - //require.Equal(t, i, blockNumber) + require.Equal(t, i, blockNumber) blockHash, err := readHash(r) require.NoError(t, err) From 79f52658d45007bc18a60feba292b4356dfbc068 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:03:54 -0800 Subject: [PATCH 107/307] measure state size --- plugin/evm/post_processing_test.go | 89 +++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f417da46ca..0f4bf4d7ae 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -8,13 +8,19 @@ import ( ) type totals struct { - blocks uint64 - txs uint64 - atomicTxs uint64 - accountReads uint64 - storageReads uint64 - accountWrites uint64 - storageWrites uint64 + blocks uint64 + txs uint64 + atomicTxs uint64 + accountReads uint64 + storageReads uint64 + accountWrites uint64 + storageWrites uint64 + accountUpdates uint64 + storageUpdates uint64 + accountDeletes uint64 + storageDeletes uint64 + accounts uint64 + storage uint64 } func TestPostProcess(t *testing.T) { @@ -23,7 +29,7 @@ func TestPostProcess(t *testing.T) { start = 1 // TODO: Verify whether genesis outs were recorded in the first block } - var totals totals + var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} for i := start; i <= end; i++ { r := fm.GetReaderFor(i) @@ -50,9 +56,20 @@ func TestPostProcess(t *testing.T) { storageWrites, err := readUint16(r) require.NoError(t, err) + accountUpdates, storageUpdates := 0, 0 + accountDeletes, storageDeletes := 0, 0 + for j := 0; j < int(accountWrites); j++ { k, v, err := readKV(r, 32) require.NoError(t, err) + if prev, ok := accountReads[string(k)]; ok { + if len(prev) > 0 { + accountUpdates++ + } + } + if len(v) == 0 { + accountDeletes++ + } if tapeVerbose { t.Logf("account write: %x -> %x", k, v) } @@ -60,32 +77,48 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(storageWrites); j++ { k, v, err := readKV(r, 64) require.NoError(t, err) + if prev, ok := storageReads[string(k)]; ok { + if len(prev) > 0 { + storageUpdates++ + } + } + if len(v) == 0 { + storageDeletes++ + } if tapeVerbose { t.Logf("storage write: %x -> %x", k, v) } } - totals.blocks++ - totals.txs += uint64(txs) - totals.atomicTxs += uint64(atomicTxs) - totals.accountReads += uint64(accountReads) - totals.storageReads += uint64(storageReads) - totals.accountWrites += uint64(accountWrites) - totals.storageWrites += uint64(storageWrites) - - t.Logf("Block[%d:%s]: totals: (txs: %d, atomic: %d, accReads: %d, storageReads: %d, accWrites: %d, storageWrites: %d)", - blockNumber, blockHash.TerminalString(), totals.txs, totals.atomicTxs, - totals.accountReads, totals.storageReads, totals.accountWrites, totals.storageWrites) + sum.blocks++ + sum.txs += uint64(txs) + sum.atomicTxs += uint64(atomicTxs) + sum.accountReads += uint64(len(accountReads)) + sum.storageReads += uint64(len(storageReads)) + sum.accountWrites += uint64(accountWrites) + sum.storageWrites += uint64(storageWrites) + sum.accountUpdates += uint64(accountUpdates) + sum.storageUpdates += uint64(storageUpdates) + sum.accountDeletes += uint64(accountDeletes) + sum.storageDeletes += uint64(storageDeletes) + sum.accounts += uint64(int(accountWrites) - accountUpdates - 2*accountDeletes) + sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) + + t.Logf("Block[%d:%s]: (txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS) = (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + blockNumber, blockHash, sum.txs, sum.atomicTxs, + sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, + sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, + sum.accounts, sum.storage) } } -func processTape(t *testing.T, r io.Reader) (uint16, int, int) { +func processTape(t *testing.T, r io.Reader) (uint16, map[string][]byte, map[string][]byte) { length, err := readUint32(r) require.NoError(t, err) pos := 0 txCount := uint16(0) - accountReads, storageReads := 0, 0 + accountReads, storageReads := make(map[string][]byte), make(map[string][]byte) for pos < int(length) { typ, err := readByte(r) require.NoError(t, err) @@ -93,15 +126,21 @@ func processTape(t *testing.T, r io.Reader) (uint16, int, int) { switch typ { case typeAccount: - _, val, err := readKV(r, 32) + key, val, err := readKV(r, 32) require.NoError(t, err) pos += 32 + 1 + len(val) - accountReads++ + k := string(key) + if _, ok := accountReads[k]; !ok { + accountReads[k] = val + } case typeStorage: - _, val, err := readKV(r, 64) + key, val, err := readKV(r, 64) require.NoError(t, err) pos += 64 + 1 + len(val) - storageReads++ + k := string(key) + if _, ok := storageReads[k]; !ok { + storageReads[k] = val + } case typeEndTx: txCount++ } From 5b1b6c1320d120d517db0577a96e0cc40b0fd7e9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:05:21 -0800 Subject: [PATCH 108/307] fix --- plugin/evm/post_processing_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 0f4bf4d7ae..a3da57c6e0 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -104,8 +104,9 @@ func TestPostProcess(t *testing.T) { sum.accounts += uint64(int(accountWrites) - accountUpdates - 2*accountDeletes) sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) - t.Logf("Block[%d:%s]: (txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS) = (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", - blockNumber, blockHash, sum.txs, sum.atomicTxs, + t.Logf("Block[%d:%s]: (txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS) = (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + blockNumber, blockHash, + sum.txs, sum.atomicTxs, sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, sum.accounts, sum.storage) From d6562f1796b0cc1b9df8183cd18e9a3c7fd48db3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:06:09 -0800 Subject: [PATCH 109/307] term str --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index a3da57c6e0..2ea9c776c3 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -105,7 +105,7 @@ func TestPostProcess(t *testing.T) { sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) t.Logf("Block[%d:%s]: (txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS) = (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", - blockNumber, blockHash, + blockNumber, blockHash.TerminalString(), sum.txs, sum.atomicTxs, sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, From c0dd1d96cb314f376e8aaa785da02a0c7f2a8966 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:16:27 -0800 Subject: [PATCH 110/307] try --- plugin/evm/post_processing_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 2ea9c776c3..a1a1d13ab4 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -62,10 +62,10 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(accountWrites); j++ { k, v, err := readKV(r, 32) require.NoError(t, err) - if prev, ok := accountReads[string(k)]; ok { - if len(prev) > 0 { - accountUpdates++ - } + prev, ok := accountReads[string(k)] + require.True(t, ok) + if len(prev) > 0 { + accountUpdates++ } if len(v) == 0 { accountDeletes++ @@ -77,10 +77,10 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(storageWrites); j++ { k, v, err := readKV(r, 64) require.NoError(t, err) - if prev, ok := storageReads[string(k)]; ok { - if len(prev) > 0 { - storageUpdates++ - } + prev, ok := storageReads[string(k)] + require.True(t, ok) + if len(prev) > 0 { + storageUpdates++ } if len(v) == 0 { storageDeletes++ From dfac973432763545d83d5b6e7d4296fbca53b8ef Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:17:59 -0800 Subject: [PATCH 111/307] try --- plugin/evm/post_processing_test.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index a1a1d13ab4..489b22b2af 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -62,10 +62,12 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(accountWrites); j++ { k, v, err := readKV(r, 32) require.NoError(t, err) - prev, ok := accountReads[string(k)] - require.True(t, ok) - if len(prev) > 0 { - accountUpdates++ + if prev, ok := accountReads[string(k)]; ok { + if len(prev) > 0 { + accountUpdates++ + } + } else { + t.Logf("account write without read: %x -> %x", k, v) } if len(v) == 0 { accountDeletes++ @@ -77,10 +79,12 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(storageWrites); j++ { k, v, err := readKV(r, 64) require.NoError(t, err) - prev, ok := storageReads[string(k)] - require.True(t, ok) - if len(prev) > 0 { - storageUpdates++ + if prev, ok := storageReads[string(k)]; ok { + if len(prev) > 0 { + storageUpdates++ + } + } else { + t.Logf("storage write without read: %x -> %x", k, v) } if len(v) == 0 { storageDeletes++ From 63135e7e9afe9b5e23847be8c8f6bd8c8a0f61c4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:28:38 -0800 Subject: [PATCH 112/307] try --- plugin/evm/post_processing_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 489b22b2af..e5d38119a1 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -63,7 +63,9 @@ func TestPostProcess(t *testing.T) { k, v, err := readKV(r, 32) require.NoError(t, err) if prev, ok := accountReads[string(k)]; ok { - if len(prev) > 0 { + if len(prev) > 0 && len(v) == 0 { + accountDeletes++ + } else if len(prev) > 0 { accountUpdates++ } } else { @@ -80,15 +82,14 @@ func TestPostProcess(t *testing.T) { k, v, err := readKV(r, 64) require.NoError(t, err) if prev, ok := storageReads[string(k)]; ok { - if len(prev) > 0 { + if len(prev) > 0 && len(v) == 0 { + storageDeletes++ + } else if len(prev) > 0 { storageUpdates++ } } else { t.Logf("storage write without read: %x -> %x", k, v) } - if len(v) == 0 { - storageDeletes++ - } if tapeVerbose { t.Logf("storage write: %x -> %x", k, v) } From 3643debefbb989c60a76edfe51b1f5a913e5517f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:30:56 -0800 Subject: [PATCH 113/307] move legend --- plugin/evm/post_processing_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index e5d38119a1..0e5af4f6c2 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -31,6 +31,7 @@ func TestPostProcess(t *testing.T) { var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} + t.Logf("(txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS)") for i := start; i <= end; i++ { r := fm.GetReaderFor(i) @@ -109,7 +110,7 @@ func TestPostProcess(t *testing.T) { sum.accounts += uint64(int(accountWrites) - accountUpdates - 2*accountDeletes) sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) - t.Logf("Block[%d:%s]: (txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS) = (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + t.Logf("Block[%d:%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", blockNumber, blockHash.TerminalString(), sum.txs, sum.atomicTxs, sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, From 2c6097c9ab28998ac254f7d7cd6f5f13073fcbd6 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 26 Dec 2024 20:34:35 -0800 Subject: [PATCH 114/307] oops --- plugin/evm/post_processing_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 0e5af4f6c2..1ea79db57f 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -72,9 +72,6 @@ func TestPostProcess(t *testing.T) { } else { t.Logf("account write without read: %x -> %x", k, v) } - if len(v) == 0 { - accountDeletes++ - } if tapeVerbose { t.Logf("account write: %x -> %x", k, v) } From 32a97bef9bf08cf821cbe348a65d23332226cc43 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 08:03:07 -0800 Subject: [PATCH 115/307] nits --- plugin/evm/post_processing_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 1ea79db57f..3dfbd4ed50 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -24,6 +24,9 @@ type totals struct { } func TestPostProcess(t *testing.T) { + if dbDir == "" { + t.Skip("No database directory provided") + } start, end := startBlock, endBlock if start == 0 { start = 1 // TODO: Verify whether genesis outs were recorded in the first block @@ -31,7 +34,7 @@ func TestPostProcess(t *testing.T) { var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} - t.Logf("(txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS)") + t.Logf("(blockNumber, txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS)") for i := start; i <= end; i++ { r := fm.GetReaderFor(i) @@ -107,7 +110,7 @@ func TestPostProcess(t *testing.T) { sum.accounts += uint64(int(accountWrites) - accountUpdates - 2*accountDeletes) sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) - t.Logf("Block[%d:%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", blockNumber, blockHash.TerminalString(), sum.txs, sum.atomicTxs, sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, From 5081cddc1416eb7b2878cc2d386117de1f977f56 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 08:03:51 -0800 Subject: [PATCH 116/307] fix --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 3dfbd4ed50..6582a1c4ee 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -111,7 +111,7 @@ func TestPostProcess(t *testing.T) { sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", - blockNumber, blockHash.TerminalString(), + blockHash.TerminalString(), blockNumber, sum.txs, sum.atomicTxs, sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, From c6c3c69831ed089675f9b6327166bf9e5dafbb59 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 15:31:25 -0800 Subject: [PATCH 117/307] DrainAcceptorQueue --- plugin/evm/reprocess_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 323dcdaecf..206891a49c 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -424,6 +424,8 @@ func reprocess( lastRoot = lastInsertedRoot lastHash = block.Hash() + bc.DrainAcceptorQueue() + // Update metadata require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) From 9fee652562072a850cac9185d4a8428781b42d42 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 16:31:22 -0800 Subject: [PATCH 118/307] try read cache --- go.mod | 3 ++ go.sum | 8 +++++ plugin/evm/post_processing_test.go | 48 +++++++++++++++++++++++++----- plugin/evm/reprocess_test.go | 4 +++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index a3f0c501c5..0496230ed7 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.8 require ( github.com/VictoriaMetrics/fastcache v1.12.1 + github.com/Yiling-J/theine-go v0.6.0 github.com/ava-labs/avalanchego v1.12.0-initial-poc.9.0.20241129153017-3f46a5a4a084 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 @@ -82,6 +83,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -110,6 +112,7 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect diff --git a/go.sum b/go.sum index 4ccdf04f33..69f9656f35 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/Yiling-J/theine-go v0.6.0 h1:jv7V/tcD6ijL0T4kfbJDKP81TCZBkoriNTPSqwivWuY= +github.com/Yiling-J/theine-go v0.6.0/go.mod h1:mdch1vjgGWd7s3rWKvY+MF5InRLfRv/CWVI9RVNQ8wY= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= @@ -355,6 +357,8 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -558,6 +562,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 6582a1c4ee..c66af2fe09 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -4,6 +4,8 @@ import ( "io" "testing" + "github.com/Yiling-J/theine-go" + "github.com/ava-labs/avalanchego/utils/units" "github.com/stretchr/testify/require" ) @@ -21,6 +23,10 @@ type totals struct { storageDeletes uint64 accounts uint64 storage uint64 + + // cache stats + accountReadHits uint64 + storageReadHits uint64 } func TestPostProcess(t *testing.T) { @@ -32,6 +38,15 @@ func TestPostProcess(t *testing.T) { start = 1 // TODO: Verify whether genesis outs were recorded in the first block } + cache, err := theine.NewBuilder[string, []byte](readCacheSize * units.MiB).Build() + require.NoError(t, err) + + cacheFn := func(k string, v []byte) bool { + _, found := cache.Get(k) + cache.Set(k, v, int64(len(k)+len(v))) + return found + } + var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} t.Logf("(blockNumber, txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS)") @@ -51,7 +66,7 @@ func TestPostProcess(t *testing.T) { atomicTxs, err := readUint16(r) require.NoError(t, err) - tapeTxs, accountReads, storageReads := processTape(t, r) + tapeTxs, accountReads, storageReads := processTape(t, r, cacheFn, &sum) require.Equal(t, txs, tapeTxs) accountWrites, err := readUint16(r) @@ -110,16 +125,27 @@ func TestPostProcess(t *testing.T) { sum.accounts += uint64(int(accountWrites) - accountUpdates - 2*accountDeletes) sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) - t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", - blockHash.TerminalString(), blockNumber, - sum.txs, sum.atomicTxs, - sum.accountReads, sum.storageReads, sum.accountWrites, sum.storageWrites, - sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, - sum.accounts, sum.storage) + if blockNumber%uint64(logEach) == 0 { + t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + blockHash.TerminalString(), blockNumber, + sum.txs, sum.atomicTxs, + sum.accountReads, sum.storageReads, + sum.accountReadHits, sum.storageReadHits, + sum.accountWrites, sum.storageWrites, + sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, + sum.accounts, sum.storage) + st := cache.Stats() + t.Logf( + "Cache stats: %d gets, %d misses, %.2f hit rate, %d entries, %d MiB", + st.Hits(), st.Misses(), st.HitRatio(), + cache.Len(), cache.EstimatedSize()/(units.MiB), + ) + } } } -func processTape(t *testing.T, r io.Reader) (uint16, map[string][]byte, map[string][]byte) { +// cache should return true if the value was found in the cache +func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, sum *totals) (uint16, map[string][]byte, map[string][]byte) { length, err := readUint32(r) require.NoError(t, err) @@ -140,6 +166,9 @@ func processTape(t *testing.T, r io.Reader) (uint16, map[string][]byte, map[stri if _, ok := accountReads[k]; !ok { accountReads[k] = val } + if cache(k, val) { + sum.accountReadHits++ + } case typeStorage: key, val, err := readKV(r, 64) require.NoError(t, err) @@ -148,6 +177,9 @@ func processTape(t *testing.T, r io.Reader) (uint16, map[string][]byte, map[stri if _, ok := storageReads[k]; !ok { storageReads[k] = val } + if cache(k, val) { + sum.storageReadHits++ + } case typeEndTx: txCount++ } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 206891a49c..7d52f8b208 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -46,6 +46,8 @@ var ( tapeVerbose = false legacyScheme = rawdb.HashScheme trieCleanCacheMBs = 0 + logEach = 1 + readCacheSize = int64(256) // merkledb options merkleDBBranchFactor = 16 @@ -70,6 +72,8 @@ func TestMain(m *testing.M) { flag.BoolVar(&tapeVerbose, "tapeVerbose", tapeVerbose, "verbose tape") flag.StringVar(&legacyScheme, "legacyScheme", legacyScheme, "legacy scheme (hash or path)") flag.IntVar(&trieCleanCacheMBs, "trieCleanCacheMBs", trieCleanCacheMBs, "clean cache size in MB") + flag.IntVar(&logEach, "logEach", logEach, "log one of each N blocks") + flag.Int64Var(&readCacheSize, "readCacheSize", readCacheSize, "read cache size in MB") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") From fb9870b4f7f7cef2949dbe02a7a49969faa05bad Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 16:32:55 -0800 Subject: [PATCH 119/307] fix --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index c66af2fe09..339fa284f4 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -30,8 +30,8 @@ type totals struct { } func TestPostProcess(t *testing.T) { - if dbDir == "" { - t.Skip("No database directory provided") + if tapeDir == "" { + t.Skip("No tape directory provided") } start, end := startBlock, endBlock if start == 0 { From 6296e0c4f0b316fdb596eb8ab41b59cbf1f8b4eb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 16:33:27 -0800 Subject: [PATCH 120/307] try --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 339fa284f4..17cfb80336 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -136,7 +136,7 @@ func TestPostProcess(t *testing.T) { sum.accounts, sum.storage) st := cache.Stats() t.Logf( - "Cache stats: %d gets, %d misses, %.2f hit rate, %d entries, %d MiB", + "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries, %d MiB", st.Hits(), st.Misses(), st.HitRatio(), cache.Len(), cache.EstimatedSize()/(units.MiB), ) From 77609e55fbae90c1b059662350ac10ffcc273ce1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 16:36:08 -0800 Subject: [PATCH 121/307] only count first access --- plugin/evm/post_processing_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 17cfb80336..e1e54bf329 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -165,9 +165,9 @@ func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, k := string(key) if _, ok := accountReads[k]; !ok { accountReads[k] = val - } - if cache(k, val) { - sum.accountReadHits++ + if cache(k, val) { + sum.accountReadHits++ + } } case typeStorage: key, val, err := readKV(r, 64) @@ -176,9 +176,9 @@ func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, k := string(key) if _, ok := storageReads[k]; !ok { storageReads[k] = val - } - if cache(k, val) { - sum.storageReadHits++ + if cache(k, val) { + sum.storageReadHits++ + } } case typeEndTx: txCount++ From 9ad00bab09043da0ee0f28085a85ccfaa4aa5105 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 16:38:13 -0800 Subject: [PATCH 122/307] no need for legend --- plugin/evm/post_processing_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index e1e54bf329..f57199d8bd 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -49,7 +49,6 @@ func TestPostProcess(t *testing.T) { var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} - t.Logf("(blockNumber, txs, atomic, readsA, readsS, writeA, writeS, upA, upS, delA, delS, sizeA, sizeS)") for i := start; i <= end; i++ { r := fm.GetReaderFor(i) From b0ce199e5f3d30a1c3876ea06b5eaa20be87e248 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 16:54:51 -0800 Subject: [PATCH 123/307] try --- plugin/evm/post_processing_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f57199d8bd..ed0456fae1 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -49,6 +49,8 @@ func TestPostProcess(t *testing.T) { var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} + + var lastReported totals for i := start; i <= end; i++ { r := fm.GetReaderFor(i) @@ -133,12 +135,14 @@ func TestPostProcess(t *testing.T) { sum.accountWrites, sum.storageWrites, sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, sum.accounts, sum.storage) - st := cache.Stats() + hits := sum.accountReadHits - lastReported.accountReadHits + sum.storageReadHits - lastReported.storageReadHits + total := sum.accountReads - lastReported.accountReads + sum.storageReads - lastReported.storageReads t.Logf( "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries, %d MiB", - st.Hits(), st.Misses(), st.HitRatio(), + hits, total-hits, float64(hits)/float64(total), cache.Len(), cache.EstimatedSize()/(units.MiB), ) + lastReported = sum } } } From 4e5c90d4dcc2cfcbe1ed56dcc9b2b2a93eb6a60b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 17:25:22 -0800 Subject: [PATCH 124/307] less allocs --- plugin/evm/post_processing_test.go | 31 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index ed0456fae1..6c9441e051 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -67,7 +67,11 @@ func TestPostProcess(t *testing.T) { atomicTxs, err := readUint16(r) require.NoError(t, err) - tapeTxs, accountReads, storageReads := processTape(t, r, cacheFn, &sum) + tapeResult := &tapeResult{ + accountReads: make(map[string][]byte), + storageReads: make(map[string][]byte), + } + tapeTxs := processTape(t, r, tapeResult, cacheFn, &sum) require.Equal(t, txs, tapeTxs) accountWrites, err := readUint16(r) @@ -82,7 +86,7 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(accountWrites); j++ { k, v, err := readKV(r, 32) require.NoError(t, err) - if prev, ok := accountReads[string(k)]; ok { + if prev, ok := tapeResult.accountReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { accountDeletes++ } else if len(prev) > 0 { @@ -98,7 +102,7 @@ func TestPostProcess(t *testing.T) { for j := 0; j < int(storageWrites); j++ { k, v, err := readKV(r, 64) require.NoError(t, err) - if prev, ok := storageReads[string(k)]; ok { + if prev, ok := tapeResult.storageReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { storageDeletes++ } else if len(prev) > 0 { @@ -115,8 +119,8 @@ func TestPostProcess(t *testing.T) { sum.blocks++ sum.txs += uint64(txs) sum.atomicTxs += uint64(atomicTxs) - sum.accountReads += uint64(len(accountReads)) - sum.storageReads += uint64(len(storageReads)) + sum.accountReads += uint64(len(tapeResult.accountReads)) + sum.storageReads += uint64(len(tapeResult.storageReads)) sum.accountWrites += uint64(accountWrites) sum.storageWrites += uint64(storageWrites) sum.accountUpdates += uint64(accountUpdates) @@ -147,14 +151,17 @@ func TestPostProcess(t *testing.T) { } } +type tapeResult struct { + accountReads, storageReads map[string][]byte +} + // cache should return true if the value was found in the cache -func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, sum *totals) (uint16, map[string][]byte, map[string][]byte) { +func processTape(t *testing.T, r io.Reader, tapeResult *tapeResult, cache func(k string, v []byte) bool, sum *totals) uint16 { length, err := readUint32(r) require.NoError(t, err) pos := 0 txCount := uint16(0) - accountReads, storageReads := make(map[string][]byte), make(map[string][]byte) for pos < int(length) { typ, err := readByte(r) require.NoError(t, err) @@ -166,8 +173,8 @@ func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, require.NoError(t, err) pos += 32 + 1 + len(val) k := string(key) - if _, ok := accountReads[k]; !ok { - accountReads[k] = val + if _, ok := tapeResult.accountReads[k]; !ok { + tapeResult.accountReads[k] = val if cache(k, val) { sum.accountReadHits++ } @@ -177,8 +184,8 @@ func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, require.NoError(t, err) pos += 64 + 1 + len(val) k := string(key) - if _, ok := storageReads[k]; !ok { - storageReads[k] = val + if _, ok := tapeResult.storageReads[k]; !ok { + tapeResult.storageReads[k] = val if cache(k, val) { sum.storageReadHits++ } @@ -187,5 +194,5 @@ func processTape(t *testing.T, r io.Reader, cache func(k string, v []byte) bool, txCount++ } } - return txCount, accountReads, storageReads + return txCount } From abd6d17f15b1da3748942fe9bd8d2b9e0f2a5245 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 17:37:17 -0800 Subject: [PATCH 125/307] log % of state --- plugin/evm/post_processing_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 6c9441e051..1d78f2cf9e 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -142,9 +142,10 @@ func TestPostProcess(t *testing.T) { hits := sum.accountReadHits - lastReported.accountReadHits + sum.storageReadHits - lastReported.storageReadHits total := sum.accountReads - lastReported.accountReads + sum.storageReads - lastReported.storageReads t.Logf( - "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries, %d MiB", + "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.2f of state), %d MiB", hits, total-hits, float64(hits)/float64(total), - cache.Len(), cache.EstimatedSize()/(units.MiB), + cache.Len(), float64(cache.Len())/float64(sum.accounts+sum.storage), + cache.EstimatedSize()/(units.MiB), ) lastReported = sum } From 42de2e0af0639d50752dfaf7f62465ede18e30b7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 17:37:42 -0800 Subject: [PATCH 126/307] add details --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 1d78f2cf9e..8ceffb741e 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -142,7 +142,7 @@ func TestPostProcess(t *testing.T) { hits := sum.accountReadHits - lastReported.accountReadHits + sum.storageReadHits - lastReported.storageReadHits total := sum.accountReads - lastReported.accountReads + sum.storageReads - lastReported.storageReads t.Logf( - "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.2f of state), %d MiB", + "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), %d MiB", hits, total-hits, float64(hits)/float64(total), cache.Len(), float64(cache.Len())/float64(sum.accounts+sum.storage), cache.EstimatedSize()/(units.MiB), From 3f752f2dca62a18ef166991cff56e5f9e95b28f8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 18:05:36 -0800 Subject: [PATCH 127/307] cmp fastcache --- plugin/evm/post_processing_test.go | 58 +++++++++++++++++++++++++----- plugin/evm/reprocess_test.go | 2 ++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 8ceffb741e..c965c4220a 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -4,6 +4,7 @@ import ( "io" "testing" + "github.com/VictoriaMetrics/fastcache" "github.com/Yiling-J/theine-go" "github.com/ava-labs/avalanchego/utils/units" "github.com/stretchr/testify/require" @@ -29,6 +30,44 @@ type totals struct { storageReadHits uint64 } +type cacheIntf interface { + Get(k string, v []byte) bool + Len() int + EstimatedSize() int +} + +type fastCache struct { + cache *fastcache.Cache +} + +func (c *fastCache) Get(k string, v []byte) bool { + found := c.cache.Has([]byte(k)) + c.cache.Set([]byte(k), v) + return found +} + +func (c *fastCache) Len() int { + var stats fastcache.Stats + c.cache.UpdateStats(&stats) + return int(stats.EntriesCount) +} + +func (c *fastCache) EstimatedSize() int { + var stats fastcache.Stats + c.cache.UpdateStats(&stats) + return int(stats.BytesSize) +} + +type theineCache struct { + *theine.Cache[string, []byte] +} + +func (c *theineCache) Get(k string, v []byte) bool { + _, found := c.Cache.Get(k) + c.Cache.Set(k, v, int64(len(k)+len(v))) + return found +} + func TestPostProcess(t *testing.T) { if tapeDir == "" { t.Skip("No tape directory provided") @@ -38,13 +77,16 @@ func TestPostProcess(t *testing.T) { start = 1 // TODO: Verify whether genesis outs were recorded in the first block } - cache, err := theine.NewBuilder[string, []byte](readCacheSize * units.MiB).Build() - require.NoError(t, err) - - cacheFn := func(k string, v []byte) bool { - _, found := cache.Get(k) - cache.Set(k, v, int64(len(k)+len(v))) - return found + var cache cacheIntf + cacheBytes := readCacheSize * units.MiB + if readCacheBackend == "fastcache" { + cache = &fastCache{cache: fastcache.New(int(cacheBytes))} + } else if readCacheBackend == "theine" { + impl, err := theine.NewBuilder[string, []byte](cacheBytes).Build() + require.NoError(t, err) + cache = &theineCache{Cache: impl} + } else { + t.Fatalf("Unknown cache backend: %s", readCacheBackend) } var sum totals @@ -71,7 +113,7 @@ func TestPostProcess(t *testing.T) { accountReads: make(map[string][]byte), storageReads: make(map[string][]byte), } - tapeTxs := processTape(t, r, tapeResult, cacheFn, &sum) + tapeTxs := processTape(t, r, tapeResult, cache.Get, &sum) require.Equal(t, txs, tapeTxs) accountWrites, err := readUint16(r) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 7d52f8b208..63cca4cf48 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -48,6 +48,7 @@ var ( trieCleanCacheMBs = 0 logEach = 1 readCacheSize = int64(256) + readCacheBackend = "theine" // merkledb options merkleDBBranchFactor = 16 @@ -74,6 +75,7 @@ func TestMain(m *testing.M) { flag.IntVar(&trieCleanCacheMBs, "trieCleanCacheMBs", trieCleanCacheMBs, "clean cache size in MB") flag.IntVar(&logEach, "logEach", logEach, "log one of each N blocks") flag.Int64Var(&readCacheSize, "readCacheSize", readCacheSize, "read cache size in MB") + flag.StringVar(&readCacheBackend, "readCacheBackend", readCacheBackend, "read cache backend (theine, fastcache)") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") From 8aac7d83e0ad76854e59674c34a9a5c1505f9366 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 19:56:31 -0800 Subject: [PATCH 128/307] add cache delete --- plugin/evm/post_processing_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index c965c4220a..4ec8932671 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -32,6 +32,7 @@ type totals struct { type cacheIntf interface { Get(k string, v []byte) bool + Delete(k string) Len() int EstimatedSize() int } @@ -58,6 +59,10 @@ func (c *fastCache) EstimatedSize() int { return int(stats.BytesSize) } +func (c *fastCache) Delete(k string) { + c.cache.Del([]byte(k)) +} + type theineCache struct { *theine.Cache[string, []byte] } @@ -131,6 +136,7 @@ func TestPostProcess(t *testing.T) { if prev, ok := tapeResult.accountReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { accountDeletes++ + cache.Delete(string(k)) } else if len(prev) > 0 { accountUpdates++ } @@ -147,6 +153,7 @@ func TestPostProcess(t *testing.T) { if prev, ok := tapeResult.storageReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { storageDeletes++ + cache.Delete(string(k)) } else if len(prev) > 0 { storageUpdates++ } From 4d1eb47849b2a6aa08cb7133a761e6f1d0d0f36e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 27 Dec 2024 21:07:50 -0800 Subject: [PATCH 129/307] try otter --- go.mod | 3 +++ go.sum | 6 ++++++ plugin/evm/post_processing_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/go.mod b/go.mod index 0496230ed7..ca229683f7 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,9 @@ require ( github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/dolthub/maphash v0.1.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/gammazero/deque v1.0.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -89,6 +91,7 @@ require ( github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/maypok86/otter v1.2.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect diff --git a/go.sum b/go.sum index 69f9656f35..c52e8f6ee8 100644 --- a/go.sum +++ b/go.sum @@ -152,6 +152,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= +github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= @@ -180,6 +182,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34= +github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= @@ -397,6 +401,8 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc= +github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 4ec8932671..0b12032987 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -7,6 +7,7 @@ import ( "github.com/VictoriaMetrics/fastcache" "github.com/Yiling-J/theine-go" "github.com/ava-labs/avalanchego/utils/units" + "github.com/maypok86/otter" "github.com/stretchr/testify/require" ) @@ -73,6 +74,24 @@ func (c *theineCache) Get(k string, v []byte) bool { return found } +type otterCache struct { + otter.Cache[string, []byte] +} + +func (c *otterCache) Get(k string, v []byte) bool { + _, found := c.Cache.Get(k) + c.Cache.Set(k, v) + return found +} + +func (c *otterCache) EstimatedSize() int { + return c.Cache.Capacity() +} + +func (c *otterCache) Len() int { + return c.Cache.Size() +} + func TestPostProcess(t *testing.T) { if tapeDir == "" { t.Skip("No tape directory provided") @@ -90,6 +109,16 @@ func TestPostProcess(t *testing.T) { impl, err := theine.NewBuilder[string, []byte](cacheBytes).Build() require.NoError(t, err) cache = &theineCache{Cache: impl} + } else if readCacheBackend == "otter" { + impl, err := otter.MustBuilder[string, []byte](int(cacheBytes)). + CollectStats(). + Cost(func(key string, value []byte) uint32 { + return uint32(len(key) + len(value)) + }).Build() + if err != nil { + panic(err) + } + cache = &otterCache{Cache: impl} } else { t.Fatalf("Unknown cache backend: %s", readCacheBackend) } From 3cda7361b91cd3a181550aaab7c5c7086d9935b5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 05:02:02 -0800 Subject: [PATCH 130/307] reduce logs --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 0b12032987..b866c30171 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -169,7 +169,7 @@ func TestPostProcess(t *testing.T) { } else if len(prev) > 0 { accountUpdates++ } - } else { + } else if tapeVerbose { t.Logf("account write without read: %x -> %x", k, v) } if tapeVerbose { @@ -186,7 +186,7 @@ func TestPostProcess(t *testing.T) { } else if len(prev) > 0 { storageUpdates++ } - } else { + } else if tapeVerbose { t.Logf("storage write without read: %x -> %x", k, v) } if tapeVerbose { From bc1899acfc2e5360deaf97fb7e46af4d7b41d9aa Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 06:16:55 -0800 Subject: [PATCH 131/307] try to figure out write recency --- go.mod | 3 +- go.sum | 2 + plugin/evm/cache_test.go | 21 ++++++++ plugin/evm/post_processing_test.go | 83 +++++++++++++++++++++++++----- plugin/evm/reprocess_test.go | 6 ++- 5 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 plugin/evm/cache_test.go diff --git a/go.mod b/go.mod index ca229683f7..ad6d88a08e 100644 --- a/go.mod +++ b/go.mod @@ -20,12 +20,14 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/hashicorp/go-bexpr v0.1.10 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.2.4 github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.17 + github.com/maypok86/otter v1.2.4 github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_model v0.3.0 @@ -91,7 +93,6 @@ require ( github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/maypok86/otter v1.2.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect diff --git a/go.sum b/go.sum index c52e8f6ee8..d1c90a981c 100644 --- a/go.sum +++ b/go.sum @@ -319,6 +319,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= diff --git a/plugin/evm/cache_test.go b/plugin/evm/cache_test.go new file mode 100644 index 0000000000..b9ad4b128e --- /dev/null +++ b/plugin/evm/cache_test.go @@ -0,0 +1,21 @@ +package evm + +import ( + "testing" + + lru "github.com/hashicorp/golang-lru/v2" + "github.com/stretchr/testify/require" +) + +func TestCacheEvictionPolicy(t *testing.T) { + onEvict := func(k uint64, v string) { + t.Logf("evicting key: %v, value: %v", k, v) + } + lru, err := lru.NewWithEvict(1024, onEvict) + require.NoError(t, err) + + for i := 0; i < 2048; i++ { + lru.Add(uint64(i%32), "value2") + lru.Add(uint64(i), "value") + } +} diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index b866c30171..ee48b94fc2 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -7,6 +7,7 @@ import ( "github.com/VictoriaMetrics/fastcache" "github.com/Yiling-J/theine-go" "github.com/ava-labs/avalanchego/utils/units" + lru "github.com/hashicorp/golang-lru/v2" "github.com/maypok86/otter" "github.com/stretchr/testify/require" ) @@ -27,8 +28,10 @@ type totals struct { storage uint64 // cache stats - accountReadHits uint64 - storageReadHits uint64 + accountReadHits uint64 + storageReadHits uint64 + accountWriteHits uint64 + storageWriteHits uint64 } type cacheIntf interface { @@ -92,6 +95,18 @@ func (c *otterCache) Len() int { return c.Cache.Size() } +type noCache struct{} + +func (c *noCache) Get(k string, v []byte) bool { return false } +func (c *noCache) Delete(k string) {} +func (c *noCache) Len() int { return 0 } +func (c *noCache) EstimatedSize() int { return 0 } + +type withUpdatedAt struct { + val []byte + updatedAt uint64 +} + func TestPostProcess(t *testing.T) { if tapeDir == "" { t.Skip("No tape directory provided") @@ -119,10 +134,21 @@ func TestPostProcess(t *testing.T) { panic(err) } cache = &otterCache{Cache: impl} + } else if readCacheBackend == "none" { + cache = &noCache{} } else { t.Fatalf("Unknown cache backend: %s", readCacheBackend) } + var writeCache *lru.Cache[string, withUpdatedAt] + var blockNumber uint64 + onEvict := func(k string, v withUpdatedAt) { + t.Logf("evicting key: %x, updatedAt: %d (%d blocks ago)", k, v.updatedAt, blockNumber-v.updatedAt) + } + + writeCache, err := lru.NewWithEvict(int(writeCacheSize), onEvict) + require.NoError(t, err) + var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} @@ -130,7 +156,8 @@ func TestPostProcess(t *testing.T) { for i := start; i <= end; i++ { r := fm.GetReaderFor(i) - blockNumber, err := readUint64(r) + var err error + blockNumber, err = readUint64(r) require.NoError(t, err) require.Equal(t, i, blockNumber) @@ -165,13 +192,24 @@ func TestPostProcess(t *testing.T) { if prev, ok := tapeResult.accountReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { accountDeletes++ - cache.Delete(string(k)) } else if len(prev) > 0 { accountUpdates++ } } else if tapeVerbose { t.Logf("account write without read: %x -> %x", k, v) } + if len(v) > 0 { + found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + if found { + sum.accountWriteHits++ + } + } else { + found := writeCache.Remove(string(k)) + if found { + sum.accountWriteHits++ + } + } + if tapeVerbose { t.Logf("account write: %x -> %x", k, v) } @@ -182,13 +220,24 @@ func TestPostProcess(t *testing.T) { if prev, ok := tapeResult.storageReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { storageDeletes++ - cache.Delete(string(k)) } else if len(prev) > 0 { storageUpdates++ } } else if tapeVerbose { t.Logf("storage write without read: %x -> %x", k, v) } + if len(v) > 0 { + found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + if found { + sum.storageWriteHits++ + } + } else { + found := writeCache.Remove(string(k)) + if found { + sum.storageWriteHits++ + } + } + if tapeVerbose { t.Logf("storage write: %x -> %x", k, v) } @@ -209,21 +258,31 @@ func TestPostProcess(t *testing.T) { sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) if blockNumber%uint64(logEach) == 0 { - t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", blockHash.TerminalString(), blockNumber, sum.txs, sum.atomicTxs, sum.accountReads, sum.storageReads, sum.accountReadHits, sum.storageReadHits, sum.accountWrites, sum.storageWrites, + sum.accountWriteHits, sum.storageWriteHits, sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, sum.accounts, sum.storage) - hits := sum.accountReadHits - lastReported.accountReadHits + sum.storageReadHits - lastReported.storageReadHits - total := sum.accountReads - lastReported.accountReads + sum.storageReads - lastReported.storageReads + if readCacheBackend != "none" { + hits := sum.accountReadHits - lastReported.accountReadHits + sum.storageReadHits - lastReported.storageReadHits + total := sum.accountReads - lastReported.accountReads + sum.storageReads - lastReported.storageReads + t.Logf( + "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), %d MiB", + hits, total-hits, float64(hits)/float64(total), + cache.Len(), float64(cache.Len())/float64(sum.accounts+sum.storage), + cache.EstimatedSize()/(units.MiB), + ) + } + writeHits := sum.accountWriteHits + sum.storageWriteHits - lastReported.accountWriteHits - lastReported.storageWriteHits + writeTotal := sum.accountWrites + sum.storageWrites - lastReported.accountWrites - lastReported.storageWrites t.Logf( - "Cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), %d MiB", - hits, total-hits, float64(hits)/float64(total), - cache.Len(), float64(cache.Len())/float64(sum.accounts+sum.storage), - cache.EstimatedSize()/(units.MiB), + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state)", + writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), + writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), ) lastReported = sum } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 63cca4cf48..fae796fb75 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -48,7 +48,8 @@ var ( trieCleanCacheMBs = 0 logEach = 1 readCacheSize = int64(256) - readCacheBackend = "theine" + readCacheBackend = "none" + writeCacheSize = uint64(1024) // merkledb options merkleDBBranchFactor = 16 @@ -75,7 +76,8 @@ func TestMain(m *testing.M) { flag.IntVar(&trieCleanCacheMBs, "trieCleanCacheMBs", trieCleanCacheMBs, "clean cache size in MB") flag.IntVar(&logEach, "logEach", logEach, "log one of each N blocks") flag.Int64Var(&readCacheSize, "readCacheSize", readCacheSize, "read cache size in MB") - flag.StringVar(&readCacheBackend, "readCacheBackend", readCacheBackend, "read cache backend (theine, fastcache)") + flag.StringVar(&readCacheBackend, "readCacheBackend", readCacheBackend, "read cache backend (theine, fastcache, otter, none)") + flag.Uint64Var(&writeCacheSize, "writeCacheSize", writeCacheSize, "write cache size in items") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") From 5a5ab62c90322ce5f34607cc2deeee534da37156 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 06:19:22 -0800 Subject: [PATCH 132/307] keep deletes --- plugin/evm/post_processing_test.go | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index ee48b94fc2..d9d8e9098b 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -198,16 +198,9 @@ func TestPostProcess(t *testing.T) { } else if tapeVerbose { t.Logf("account write without read: %x -> %x", k, v) } - if len(v) > 0 { - found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) - if found { - sum.accountWriteHits++ - } - } else { - found := writeCache.Remove(string(k)) - if found { - sum.accountWriteHits++ - } + found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + if found { + sum.accountWriteHits++ } if tapeVerbose { @@ -226,16 +219,9 @@ func TestPostProcess(t *testing.T) { } else if tapeVerbose { t.Logf("storage write without read: %x -> %x", k, v) } - if len(v) > 0 { - found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) - if found { - sum.storageWriteHits++ - } - } else { - found := writeCache.Remove(string(k)) - if found { - sum.storageWriteHits++ - } + found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + if found { + sum.storageWriteHits++ } if tapeVerbose { From 31ca4bbf0acb1e0c99ddd30b2730a661f0ec0c5b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 06:28:54 -0800 Subject: [PATCH 133/307] count 0 -> 0 as "update" (vs deletion or addition --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index d9d8e9098b..f8cfd8f28d 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -192,7 +192,7 @@ func TestPostProcess(t *testing.T) { if prev, ok := tapeResult.accountReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { accountDeletes++ - } else if len(prev) > 0 { + } else if len(prev) > 0 || (len(prev) == 0 && len(v) == 0) { accountUpdates++ } } else if tapeVerbose { @@ -213,7 +213,7 @@ func TestPostProcess(t *testing.T) { if prev, ok := tapeResult.storageReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { storageDeletes++ - } else if len(prev) > 0 { + } else if len(prev) > 0 || (len(prev) == 0 && len(v) == 0) { storageUpdates++ } } else if tapeVerbose { From 39699770981cb9a09a59f13dfc6993c0c0aabcc4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 06:32:02 -0800 Subject: [PATCH 134/307] more logging --- plugin/evm/post_processing_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f8cfd8f28d..a651da0f38 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -107,6 +107,11 @@ type withUpdatedAt struct { updatedAt uint64 } +func short(s string) string { + // return first 2 and last 2 characters + return s[:2] + "..." + s[len(s)-2:] +} + func TestPostProcess(t *testing.T) { if tapeDir == "" { t.Skip("No tape directory provided") @@ -143,7 +148,7 @@ func TestPostProcess(t *testing.T) { var writeCache *lru.Cache[string, withUpdatedAt] var blockNumber uint64 onEvict := func(k string, v withUpdatedAt) { - t.Logf("evicting key: %x, updatedAt: %d (%d blocks ago)", k, v.updatedAt, blockNumber-v.updatedAt) + t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) } writeCache, err := lru.NewWithEvict(int(writeCacheSize), onEvict) From 678ba402dadce6f160a92d496ad0f29c21d4cf39 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 06:36:53 -0800 Subject: [PATCH 135/307] stats --- plugin/evm/post_processing_test.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index a651da0f38..bcf4148578 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -28,10 +28,12 @@ type totals struct { storage uint64 // cache stats - accountReadHits uint64 - storageReadHits uint64 - accountWriteHits uint64 - storageWriteHits uint64 + accountReadHits uint64 + storageReadHits uint64 + accountWriteHits uint64 + storageWriteHits uint64 + writeCacheEvictAccount uint64 + writeCacheEvictStorage uint64 } type cacheIntf interface { @@ -146,15 +148,20 @@ func TestPostProcess(t *testing.T) { } var writeCache *lru.Cache[string, withUpdatedAt] + var sum totals var blockNumber uint64 onEvict := func(k string, v withUpdatedAt) { - t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) + if len(k) == 32 { + sum.writeCacheEvictAccount++ + } else { + sum.writeCacheEvictStorage++ + } + // t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) } writeCache, err := lru.NewWithEvict(int(writeCacheSize), onEvict) require.NoError(t, err) - var sum totals fm := &fileManager{dir: tapeDir, newEach: 10_000} var lastReported totals @@ -270,10 +277,14 @@ func TestPostProcess(t *testing.T) { } writeHits := sum.accountWriteHits + sum.storageWriteHits - lastReported.accountWriteHits - lastReported.storageWriteHits writeTotal := sum.accountWrites + sum.storageWrites - lastReported.accountWrites - lastReported.storageWrites + _, oldest, _ := writeCache.GetOldest() t.Logf( - "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state)", + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted: %d accounts, %d storage (oldest updatedAt: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), + sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount, + sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage, + oldest.updatedAt, ) lastReported = sum } From 25d91eff3f0d1e3626fcc924add5243956ad34dc Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 06:52:01 -0800 Subject: [PATCH 136/307] per tx --- plugin/evm/post_processing_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index bcf4148578..58c802befd 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -278,12 +278,13 @@ func TestPostProcess(t *testing.T) { writeHits := sum.accountWriteHits + sum.storageWriteHits - lastReported.accountWriteHits - lastReported.storageWriteHits writeTotal := sum.accountWrites + sum.storageWrites - lastReported.accountWrites - lastReported.storageWrites _, oldest, _ := writeCache.GetOldest() + txs := sum.txs - lastReported.txs t.Logf( - "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted: %d accounts, %d storage (oldest updatedAt: %d)", + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (oldest updatedAt: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), - sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount, - sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage, + float64(sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount)/float64(txs), + float64(sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage)/float64(txs), oldest.updatedAt, ) lastReported = sum From 22c1190f15898c5a6d71f5e57186f5554c46bcef Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 07:08:02 -0800 Subject: [PATCH 137/307] log --- plugin/evm/post_processing_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 58c802befd..eaff4ab2b5 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -280,11 +280,12 @@ func TestPostProcess(t *testing.T) { _, oldest, _ := writeCache.GetOldest() txs := sum.txs - lastReported.txs t.Logf( - "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (oldest updatedAt: %d)", + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (oldest updatedAt: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), float64(sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount)/float64(txs), float64(sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage)/float64(txs), + (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, oldest.updatedAt, ) lastReported = sum From 280ecc08044913ac306b24c14b55f4f8efe53c01 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:04:58 -0800 Subject: [PATCH 138/307] try hst --- go.mod | 2 ++ go.sum | 4 ++++ plugin/evm/post_processing_test.go | 23 +++++++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ad6d88a08e..9009b5d695 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tyler-smith/go-bip39 v1.1.0 github.com/urfave/cli/v2 v2.25.7 + github.com/valyala/histogram v1.2.0 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 golang.org/x/crypto v0.26.0 @@ -114,6 +115,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/valyala/fastrand v1.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect diff --git a/go.sum b/go.sum index d1c90a981c..ffd988a800 100644 --- a/go.sum +++ b/go.sum @@ -549,8 +549,12 @@ github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6S github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index eaff4ab2b5..bb29ffc801 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -10,6 +10,7 @@ import ( lru "github.com/hashicorp/golang-lru/v2" "github.com/maypok86/otter" "github.com/stretchr/testify/require" + "github.com/valyala/histogram" ) type totals struct { @@ -150,6 +151,8 @@ func TestPostProcess(t *testing.T) { var writeCache *lru.Cache[string, withUpdatedAt] var sum totals var blockNumber uint64 + hst := histogram.NewFast() + inf := float64(100_000_000) onEvict := func(k string, v withUpdatedAt) { if len(k) == 32 { sum.writeCacheEvictAccount++ @@ -210,7 +213,13 @@ func TestPostProcess(t *testing.T) { } else if tapeVerbose { t.Logf("account write without read: %x -> %x", k, v) } - found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + got, found := writeCache.Get(string(k)) + if found { + hst.Update(float64(blockNumber - got.updatedAt)) + } else { + hst.Update(inf) + } + writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { sum.accountWriteHits++ } @@ -231,7 +240,13 @@ func TestPostProcess(t *testing.T) { } else if tapeVerbose { t.Logf("storage write without read: %x -> %x", k, v) } - found, _ := writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + got, found := writeCache.Get(string(k)) + if found { + hst.Update(float64(blockNumber - got.updatedAt)) + } else { + hst.Update(inf) + } + writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { sum.storageWriteHits++ } @@ -288,6 +303,10 @@ func TestPostProcess(t *testing.T) { (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, oldest.updatedAt, ) + quants := []float64{0.5, 0.9, 0.99, 0.999} + for _, q := range quants { + t.Logf("Eviction histogram quantile %.3f: %.1f", q, hst.Quantile(q)) + } lastReported = sum } } From f0d5a661e62c36433a09e836575a27e3f11d26e2 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:08:01 -0800 Subject: [PATCH 139/307] try --- plugin/evm/post_processing_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index bb29ffc801..5feb109f34 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -1,6 +1,7 @@ package evm import ( + "fmt" "io" "testing" @@ -303,10 +304,12 @@ func TestPostProcess(t *testing.T) { (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, oldest.updatedAt, ) - quants := []float64{0.5, 0.9, 0.99, 0.999} + quants := []float64{0.5, 0.7, 0.8, 0.9} + var outString string for _, q := range quants { - t.Logf("Eviction histogram quantile %.3f: %.1f", q, hst.Quantile(q)) + outString = fmt.Sprintf("%s [%f %f]", outString, q, hst.Quantile(q)) } + t.Logf("Write cache quantiles: %s", outString) lastReported = sum } } From edc8f28abb2b4a233d4db074d0cf3aeaaf3d915f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:08:44 -0800 Subject: [PATCH 140/307] improve log --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 5feb109f34..0b9c0c225e 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -307,7 +307,7 @@ func TestPostProcess(t *testing.T) { quants := []float64{0.5, 0.7, 0.8, 0.9} var outString string for _, q := range quants { - outString = fmt.Sprintf("%s [%f %f]", outString, q, hst.Quantile(q)) + outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hst.Quantile(q))) } t.Logf("Write cache quantiles: %s", outString) lastReported = sum From 44b2440ce95aebae02e6262b9e6daaab25e26358 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:10:45 -0800 Subject: [PATCH 141/307] try --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 0b9c0c225e..84f5bc12b9 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -304,7 +304,7 @@ func TestPostProcess(t *testing.T) { (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, oldest.updatedAt, ) - quants := []float64{0.5, 0.7, 0.8, 0.9} + quants := []float64{0.1, 0.25, 0.5, 0.7, 0.8, 0.9} var outString string for _, q := range quants { outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hst.Quantile(q))) From b05e69b967f81d377825812a05f24d7367f293f4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:10:58 -0800 Subject: [PATCH 142/307] try --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 84f5bc12b9..99bfd01cfe 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -304,7 +304,7 @@ func TestPostProcess(t *testing.T) { (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, oldest.updatedAt, ) - quants := []float64{0.1, 0.25, 0.5, 0.7, 0.8, 0.9} + quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.8, 0.9} var outString string for _, q := range quants { outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hst.Quantile(q))) From 387a3278b2b01d02df9c27b9a1a54d61de138bfb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:17:14 -0800 Subject: [PATCH 143/307] two hist --- plugin/evm/post_processing_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 99bfd01cfe..8b0d5e5e4b 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -153,6 +153,7 @@ func TestPostProcess(t *testing.T) { var sum totals var blockNumber uint64 hst := histogram.NewFast() + hstWithReset := histogram.NewFast() inf := float64(100_000_000) onEvict := func(k string, v withUpdatedAt) { if len(k) == 32 { @@ -217,8 +218,10 @@ func TestPostProcess(t *testing.T) { got, found := writeCache.Get(string(k)) if found { hst.Update(float64(blockNumber - got.updatedAt)) + hstWithReset.Update(float64(blockNumber - got.updatedAt)) } else { hst.Update(inf) + hstWithReset.Update(inf) } writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { @@ -244,8 +247,10 @@ func TestPostProcess(t *testing.T) { got, found := writeCache.Get(string(k)) if found { hst.Update(float64(blockNumber - got.updatedAt)) + hstWithReset.Update(float64(blockNumber - got.updatedAt)) } else { hst.Update(inf) + hstWithReset.Update(inf) } writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { @@ -310,6 +315,11 @@ func TestPostProcess(t *testing.T) { outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hst.Quantile(q))) } t.Logf("Write cache quantiles: %s", outString) + for _, q := range quants { + outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hstWithReset.Quantile(q))) + } + t.Logf("Reset cache quantiles: %s", outString) + hstWithReset.Reset() lastReported = sum } } From 4fdcbacb0ae94bfc320ed84978a1e4f14f660725 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:18:23 -0800 Subject: [PATCH 144/307] oops --- plugin/evm/post_processing_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 8b0d5e5e4b..da9163aed4 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -315,6 +315,7 @@ func TestPostProcess(t *testing.T) { outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hst.Quantile(q))) } t.Logf("Write cache quantiles: %s", outString) + outString = "" for _, q := range quants { outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hstWithReset.Quantile(q))) } From 2f5719691f31855d4cd14e1a0a29cbe9ccc64988 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:25:15 -0800 Subject: [PATCH 145/307] bump inf --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index da9163aed4..b50a78ed46 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -154,7 +154,7 @@ func TestPostProcess(t *testing.T) { var blockNumber uint64 hst := histogram.NewFast() hstWithReset := histogram.NewFast() - inf := float64(100_000_000) + inf := float64(1_000_000_000) onEvict := func(k string, v withUpdatedAt) { if len(k) == 32 { sum.writeCacheEvictAccount++ From b3028aeefd97279d7cdc6b4d8cfd04ac4ea63e55 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:28:54 -0800 Subject: [PATCH 146/307] try fix --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index b50a78ed46..eba54ff193 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -223,7 +223,7 @@ func TestPostProcess(t *testing.T) { hst.Update(inf) hstWithReset.Update(inf) } - writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + writeCache.Add(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { sum.accountWriteHits++ } @@ -252,7 +252,7 @@ func TestPostProcess(t *testing.T) { hst.Update(inf) hstWithReset.Update(inf) } - writeCache.ContainsOrAdd(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + writeCache.Add(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { sum.storageWriteHits++ } From 7b221ae5581c12d49c0814d92d9f27942effc9bd Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:30:50 -0800 Subject: [PATCH 147/307] formatting --- plugin/evm/post_processing_test.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index eba54ff193..2a9cac0d99 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -309,15 +309,25 @@ func TestPostProcess(t *testing.T) { (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, oldest.updatedAt, ) - quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.8, 0.9} + quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.8, 0.9, 0.95, 0.99} var outString string for _, q := range quants { - outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hst.Quantile(q))) + val := hst.Quantile(q) + if val == inf { + outString = fmt.Sprintf("%s [%.2f inf]", outString, q) + continue + } + outString = fmt.Sprintf("%s [%.2f %d]", outString, q, val) } t.Logf("Write cache quantiles: %s", outString) outString = "" for _, q := range quants { - outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(hstWithReset.Quantile(q))) + val := hst.Quantile(q) + if val == inf { + outString = fmt.Sprintf("%s [%.2f inf]", outString, q) + continue + } + outString = fmt.Sprintf("%s [%.2f %d]", outString, q, val) } t.Logf("Reset cache quantiles: %s", outString) hstWithReset.Reset() From 01523a831be58bfe8ef85bf15c7d49c0896d3520 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:31:17 -0800 Subject: [PATCH 148/307] fix --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 2a9cac0d99..7367284907 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -317,7 +317,7 @@ func TestPostProcess(t *testing.T) { outString = fmt.Sprintf("%s [%.2f inf]", outString, q) continue } - outString = fmt.Sprintf("%s [%.2f %d]", outString, q, val) + outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(val)) } t.Logf("Write cache quantiles: %s", outString) outString = "" @@ -327,7 +327,7 @@ func TestPostProcess(t *testing.T) { outString = fmt.Sprintf("%s [%.2f inf]", outString, q) continue } - outString = fmt.Sprintf("%s [%.2f %d]", outString, q, val) + outString = fmt.Sprintf("%s [%.2f %d]", outString, q, int(val)) } t.Logf("Reset cache quantiles: %s", outString) hstWithReset.Reset() From cbdb5684c01eaa958b467d6e3fb5acf7d6be7d3b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:32:03 -0800 Subject: [PATCH 149/307] fix --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 7367284907..3fea6fa9f7 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -322,7 +322,7 @@ func TestPostProcess(t *testing.T) { t.Logf("Write cache quantiles: %s", outString) outString = "" for _, q := range quants { - val := hst.Quantile(q) + val := hstWithReset.Quantile(q) if val == inf { outString = fmt.Sprintf("%s [%.2f inf]", outString, q) continue From ea6b5bd7b5973a2a021245ad098e683be2842f76 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 28 Dec 2024 08:44:35 -0800 Subject: [PATCH 150/307] try --- plugin/evm/post_processing_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 3fea6fa9f7..7ee3829886 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -301,15 +301,15 @@ func TestPostProcess(t *testing.T) { _, oldest, _ := writeCache.GetOldest() txs := sum.txs - lastReported.txs t.Logf( - "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (oldest updatedAt: %d)", + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (oldest age: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), float64(sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount)/float64(txs), float64(sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage)/float64(txs), (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, - oldest.updatedAt, + blockNumber-oldest.updatedAt, ) - quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.8, 0.9, 0.95, 0.99} + quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95} var outString string for _, q := range quants { val := hst.Quantile(q) From 5718b883cc6da1fa3066d6595e73f243f321d5c6 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 30 Dec 2024 17:05:25 -0800 Subject: [PATCH 151/307] try bufio --- plugin/evm/post_processing_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 7ee3829886..b8db3d2960 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -1,6 +1,7 @@ package evm import ( + "bufio" "fmt" "io" "testing" @@ -172,6 +173,7 @@ func TestPostProcess(t *testing.T) { var lastReported totals for i := start; i <= end; i++ { r := fm.GetReaderFor(i) + r = bufio.NewReaderSize(r, 1<<20) // 1 MiB buffer var err error blockNumber, err = readUint64(r) From c7a456946c2d74ebd1e46a475a5909f2ecf0f29d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 30 Dec 2024 17:08:07 -0800 Subject: [PATCH 152/307] try --- plugin/evm/post_processing_test.go | 2 -- plugin/evm/reprocess_recording_test.go | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index b8db3d2960..7ee3829886 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -1,7 +1,6 @@ package evm import ( - "bufio" "fmt" "io" "testing" @@ -173,7 +172,6 @@ func TestPostProcess(t *testing.T) { var lastReported totals for i := start; i <= end; i++ { r := fm.GetReaderFor(i) - r = bufio.NewReaderSize(r, 1<<20) // 1 MiB buffer var err error blockNumber, err = readUint64(r) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 734026ec0a..76c8553ad4 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -1,6 +1,7 @@ package evm import ( + "bufio" "encoding/binary" "fmt" "io" @@ -273,6 +274,7 @@ type fileManager struct { newEach uint64 lastFile uint64 f *os.File + reader io.Reader } func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { @@ -295,7 +297,7 @@ func (f *fileManager) GetWriterFor(blockNumber uint64) io.Writer { func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { group := blockNumber - blockNumber%f.newEach if group == f.lastFile && f.f != nil { - return f.f + return f.reader } if f.f != nil { f.f.Close() @@ -306,7 +308,8 @@ func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { } f.f = file f.lastFile = group - return f.f + f.reader = bufio.NewReaderSize(f.f, 1024*1024) + return f.reader } func (f *fileManager) Close() error { From b5616d13f061789ac27a7fceebd107197cb66a7d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 30 Dec 2024 17:11:25 -0800 Subject: [PATCH 153/307] try --- plugin/evm/reprocess_recording_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 76c8553ad4..181bbdf8f2 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -308,7 +308,7 @@ func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { } f.f = file f.lastFile = group - f.reader = bufio.NewReaderSize(f.f, 1024*1024) + f.reader = bufio.NewReader(file) return f.reader } From 8590c8e76cb4290ec8a369e74a73d87048f5e669 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 30 Dec 2024 17:11:51 -0800 Subject: [PATCH 154/307] try --- plugin/evm/reprocess_recording_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 181bbdf8f2..e7620cc8e2 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -1,7 +1,6 @@ package evm import ( - "bufio" "encoding/binary" "fmt" "io" @@ -308,7 +307,7 @@ func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { } f.f = file f.lastFile = group - f.reader = bufio.NewReader(file) + f.reader = file return f.reader } From 67acfe079c64efff66123b2b405a8f69701e9f92 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 30 Dec 2024 17:16:58 -0800 Subject: [PATCH 155/307] try again --- plugin/evm/reprocess_recording_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index e7620cc8e2..3f796a9bf2 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -1,6 +1,7 @@ package evm import ( + "bufio" "encoding/binary" "fmt" "io" @@ -161,31 +162,31 @@ func writeUint64(w io.Writer, i uint64) error { func readByte(r io.Reader) (byte, error) { buf := make([]byte, 1) - _, err := r.Read(buf) + _, err := io.ReadFull(r, buf) return buf[0], err } func readUint16(r io.Reader) (uint16, error) { buf := make([]byte, 2) - _, err := r.Read(buf) + _, err := io.ReadFull(r, buf) return binary.BigEndian.Uint16(buf), err } func readUint32(r io.Reader) (uint32, error) { buf := make([]byte, 4) - _, err := r.Read(buf) + _, err := io.ReadFull(r, buf) return binary.BigEndian.Uint32(buf), err } func readUint64(r io.Reader) (uint64, error) { buf := make([]byte, 8) - _, err := r.Read(buf) + _, err := io.ReadFull(r, buf) return binary.BigEndian.Uint64(buf), err } func readKV(r io.Reader, keyLen int) ([]byte, []byte, error) { key := make([]byte, keyLen) - _, err := r.Read(key) + _, err := io.ReadFull(r, key) if err != nil { return nil, nil, err } @@ -194,13 +195,13 @@ func readKV(r io.Reader, keyLen int) ([]byte, []byte, error) { return nil, nil, err } val := make([]byte, valLen) - _, err = r.Read(val) + _, err = io.ReadFull(r, val) return key, val, err } func readHash(r io.Reader) (common.Hash, error) { var h common.Hash - _, err := r.Read(h[:]) + _, err := io.ReadFull(r, h[:]) return h, err } @@ -307,7 +308,7 @@ func (f *fileManager) GetReaderFor(blockNumber uint64) io.Reader { } f.f = file f.lastFile = group - f.reader = file + f.reader = bufio.NewReaderSize(f.f, 1024*1024) return f.reader } From 5ee41524b94e31a3d9f5f3e39370dc5175063fa7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 11:25:34 -0800 Subject: [PATCH 156/307] try --- plugin/evm/reprocess_backend_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index a0810619e2..61cbe4159d 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -156,6 +156,7 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) // Update the chain config with mainnet upgrades g.Config = params.GetChainConfig(upgrade.Mainnet, g.Config.ChainID) + t.Logf("Mainnet chain config: %v", g.Config) testVM := &VM{ chainConfig: g.Config, From 605aec2d7390504c5941b4206193949d6d56622f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 11:27:52 -0800 Subject: [PATCH 157/307] try --- plugin/evm/reprocess_backend_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 61cbe4159d..ea60aaf13b 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + warpcontract "github.com/ava-labs/coreth/precompile/contracts/warp" "github.com/ava-labs/coreth/shim/merkledb" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" @@ -156,6 +157,13 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) // Update the chain config with mainnet upgrades g.Config = params.GetChainConfig(upgrade.Mainnet, g.Config.ChainID) + // If the Durango is activated, activate the Warp Precompile at the same time + if g.Config.DurangoBlockTimestamp != nil { + g.Config.PrecompileUpgrades = append(g.Config.PrecompileUpgrades, params.PrecompileUpgrade{ + Config: warpcontract.NewDefaultConfig(g.Config.DurangoBlockTimestamp), + }) + } + t.Logf("Mainnet chain config: %v", g.Config) testVM := &VM{ From d4f7f9c0c466679ae308e789231461c2cb1c2d8f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 11:29:39 -0800 Subject: [PATCH 158/307] try --- plugin/evm/reprocess_backend_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index ea60aaf13b..99cb526041 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -169,7 +169,10 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs testVM := &VM{ chainConfig: g.Config, codec: Codec, - ctx: &snow.Context{AVAXAssetID: mainnetAvaxAssetID}, + ctx: &snow.Context{ + AVAXAssetID: mainnetAvaxAssetID, + ChainID: mainnetCChainID, + }, } cbs := dummy.ConsensusCallbacks{OnExtraStateChange: testVM.onExtraStateChange} engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ModeSkipHeader: true}) From 69908df1ab8486be7cc4fc41fc8a59ba67e07fcf Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 11:31:11 -0800 Subject: [PATCH 159/307] try --- plugin/evm/reprocess_backend_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 99cb526041..f06da70778 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -163,16 +163,17 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs Config: warpcontract.NewDefaultConfig(g.Config.DurangoBlockTimestamp), }) } + g.Config.SnowCtx = &snow.Context{ + AVAXAssetID: mainnetAvaxAssetID, + ChainID: mainnetCChainID, + } t.Logf("Mainnet chain config: %v", g.Config) testVM := &VM{ chainConfig: g.Config, codec: Codec, - ctx: &snow.Context{ - AVAXAssetID: mainnetAvaxAssetID, - ChainID: mainnetCChainID, - }, + ctx: g.Config.SnowCtx, } cbs := dummy.ConsensusCallbacks{OnExtraStateChange: testVM.onExtraStateChange} engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ModeSkipHeader: true}) From fd758a91832e47dc4dd58cbdb0ba562f46f934a4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 11:35:26 -0800 Subject: [PATCH 160/307] try --- plugin/evm/reprocess_backend_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index f06da70778..fc3368e900 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/upgrade" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/units" xmerkledb "github.com/ava-labs/avalanchego/x/merkledb" "github.com/ava-labs/coreth/consensus" @@ -166,6 +167,7 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs g.Config.SnowCtx = &snow.Context{ AVAXAssetID: mainnetAvaxAssetID, ChainID: mainnetCChainID, + NetworkID: constants.MainnetID, } t.Logf("Mainnet chain config: %v", g.Config) From 41678e10bd141342ab7e991d5505276b536c4820 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 20:26:25 -0800 Subject: [PATCH 161/307] basic ipc backend --- plugin/evm/reprocess_backend_test.go | 16 ++- plugin/evm/reprocess_test.go | 8 +- shim/nomt/message.proto | 59 +++++++++++ shim/nomt/nomt.go | 146 +++++++++++++++++++++++++++ shim/nomt/nomt_test.go | 66 ++++++++++++ 5 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 shim/nomt/message.proto create mode 100644 shim/nomt/nomt.go create mode 100644 shim/nomt/nomt_test.go diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index fc3368e900..76d6f7ceaa 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/json" "math/big" + "net" "testing" "github.com/ava-labs/avalanchego/database" @@ -24,6 +25,7 @@ import ( "github.com/ava-labs/coreth/params" warpcontract "github.com/ava-labs/coreth/precompile/contracts/warp" "github.com/ava-labs/coreth/shim/merkledb" + "github.com/ava-labs/coreth/shim/nomt" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -41,8 +43,6 @@ type reprocessBackend struct { Disk ethdb.Database Metadata database.Database Name string - - mdb xmerkledb.MerkleDB } func getMerkleDB(t *testing.T, mdbKVStore database.Database) xmerkledb.MerkleDB { @@ -140,6 +140,11 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB merkleDB = getMerkleDB(t, dbs.merkledb) kvBackend = merkledb.NewMerkleDB(merkleDB) } + if name == "nomt" { + conn, err := net.Dial("unix", socketPath) + require.NoError(t, err) + kvBackend = nomt.New(conn) + } return &reprocessBackend{ Genesis: g, Engine: engine, @@ -149,7 +154,6 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB Metadata: dbs.metadata, Name: name, VerifyRoot: name == "legacy", - mdb: merkleDB, } } @@ -188,6 +192,11 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs merkleDB = getMerkleDB(t, dbs.merkledb) kvBackend = merkledb.NewMerkleDB(merkleDB) } + if name == "nomt" { + conn, err := net.Dial("unix", socketPath) + require.NoError(t, err) + kvBackend = nomt.New(conn) + } return &reprocessBackend{ Genesis: &g, Engine: engine, @@ -202,6 +211,5 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs Metadata: dbs.metadata, Name: name, VerifyRoot: name == "legacy", - mdb: merkleDB, } } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index fae796fb75..c88ab2bf11 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -57,6 +57,9 @@ var ( intermediateNodeCacheSizeMB = 1 intermediateWriteBufferSizeKB = 1024 intermediateWriteBatchSizeKB = 256 + + // ipc options + socketPath = "/tmp/rust_socket" ) func TestMain(m *testing.M) { @@ -78,6 +81,7 @@ func TestMain(m *testing.M) { flag.Int64Var(&readCacheSize, "readCacheSize", readCacheSize, "read cache size in MB") flag.StringVar(&readCacheBackend, "readCacheBackend", readCacheBackend, "read cache backend (theine, fastcache, otter, none)") flag.Uint64Var(&writeCacheSize, "writeCacheSize", writeCacheSize, "write cache size in items") + flag.StringVar(&socketPath, "socketPath", socketPath, "socket path") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -267,7 +271,8 @@ func CleanupOnInterrupt(cleanup func()) { } func TestReprocessGenesis(t *testing.T) { - for _, backend := range []string{"merkledb", "legacy"} { + // nomt commented out as needs separate process to function + for _, backend := range []string{"merkledb", "legacy" /* , "nomt" */} { t.Run(backend, func(t *testing.T) { testReprocessGenesis(t, backend) }) } } @@ -293,6 +298,7 @@ func TestReprocessMainnetBlocks(t *testing.T) { } for _, backend := range []*reprocessBackend{ + getMainnetBackend(t, "nomt", source, dbs), getMainnetBackend(t, "merkledb", source, dbs), getMainnetBackend(t, "legacy", source, dbs), } { diff --git a/shim/nomt/message.proto b/shim/nomt/message.proto new file mode 100644 index 0000000000..364c6e9305 --- /dev/null +++ b/shim/nomt/message.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package database_interface; + +message Request { + oneof request { + RootRequest root = 1; + GetRequest get = 2; + PrefetchRequest prefetch = 3; + UpdateRequest update = 4; + CloseRequest close = 5; + } +} + +message Response { + int32 err_code = 1; + oneof response { + RootResponse root = 2; + GetResponse get = 3; + PrefetchResponse prefetch = 4; + UpdateResponse update = 5; + CloseResponse close = 6; + } +} + +message RootRequest {} +message RootResponse { + bytes root = 1; +} + +message GetRequest { + bytes key = 1; +} + +message GetResponse { + bytes value = 2; +} + +message PrefetchRequest { + bytes key = 1; +} + +message PrefetchResponse {} + +message UpdateRequestItem { + bytes key = 1; + bytes value = 2; +} + +message UpdateRequest { + repeated UpdateRequestItem items = 1; +} + +message UpdateResponse { + bytes root = 2; +} + +message CloseRequest { } +message CloseResponse { } \ No newline at end of file diff --git a/shim/nomt/nomt.go b/shim/nomt/nomt.go new file mode 100644 index 0000000000..e6d758d367 --- /dev/null +++ b/shim/nomt/nomt.go @@ -0,0 +1,146 @@ +package nomt + +import ( + "encoding/binary" + "net" + "sync" + + "github.com/ava-labs/coreth/shim/nomt/nomt" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "google.golang.org/protobuf/proto" +) + +var _ triedb.KVBackend = &Nomt{} + +const maxReponseSize = 1024 + +func response(conn net.Conn, req *nomt.Request) (*nomt.Response, error) { + data, err := proto.Marshal(req) + if err != nil { + return nil, err + } + if _, err := conn.Write(binary.BigEndian.AppendUint32(nil, uint32(len(data)))); err != nil { + return nil, err + } + if _, err := conn.Write(data); err != nil { + return nil, err + } + + respData := make([]byte, maxReponseSize) + n, err := conn.Read(respData) + if err != nil { + return nil, err + } + var resp nomt.Response + if err := proto.Unmarshal(respData[:n], &resp); err != nil { + return nil, err + } + return &resp, nil +} + +type Nomt struct { + lock sync.RWMutex + conn net.Conn +} + +func New(conn net.Conn) *Nomt { + return &Nomt{ + conn: conn, + } +} + +func (n *Nomt) response(req *nomt.Request) (*nomt.Response, error) { + n.lock.Lock() + defer n.lock.Unlock() + + return response(n.conn, req) +} + +func (n *Nomt) Root() common.Hash { + req := &nomt.Request{ + Request: &nomt.Request_Root{ + Root: &nomt.RootRequest{}, + }, + } + resp, err := n.response(req) + if err != nil { + log.Error("Failed to get root", "err", err) + return common.Hash{} + } + return common.BytesToHash(resp.GetRoot().Root) +} + +func (n *Nomt) Get(key []byte) ([]byte, error) { + req := &nomt.Request{ + Request: &nomt.Request_Get{ + Get: &nomt.GetRequest{ + Key: key, + }, + }, + } + + resp, err := n.response(req) + if err != nil { + return nil, err + } + if resp.GetErrCode() == 1 { // Not found + return nil, nil + } + return resp.GetGet().Value, nil +} + +func (n *Nomt) Prefetch(key []byte) ([]byte, error) { + req := &nomt.Request{ + Request: &nomt.Request_Prefetch{ + Prefetch: &nomt.PrefetchRequest{ + Key: key, + }, + }, + } + + _, err := n.response(req) + return nil, err +} + +func (n *Nomt) Update(batch triedb.Batch) (common.Hash, error) { + req := &nomt.Request{ + Request: &nomt.Request_Update{ + Update: &nomt.UpdateRequest{ + Items: make([]*nomt.UpdateRequestItem, len(batch)), + }, + }, + } + + for i, item := range batch { + req.GetUpdate().Items[i] = &nomt.UpdateRequestItem{ + Key: item.Key, + Value: item.Value, + } + } + + resp, err := n.response(req) + if err != nil { + return common.Hash{}, err + } + return common.BytesToHash(resp.GetUpdate().Root), nil +} + +func (n *Nomt) Commit(root common.Hash) error { + return nil +} + +func (n *Nomt) Close() error { + close := &nomt.Request{ + Request: &nomt.Request_Close{ + Close: &nomt.CloseRequest{}, + }, + } + _, err := n.response(close) + if err != nil { + return err + } + + return n.conn.Close() +} diff --git a/shim/nomt/nomt_test.go b/shim/nomt/nomt_test.go new file mode 100644 index 0000000000..fb6beb9dd5 --- /dev/null +++ b/shim/nomt/nomt_test.go @@ -0,0 +1,66 @@ +package nomt + +import ( + "fmt" + "net" + "testing" + + "github.com/ava-labs/coreth/shim/nomt/nomt" + "github.com/stretchr/testify/require" +) + +//go:generate protoc --go_out=. --go_opt=Mmessage.proto=./nomt message.proto + +const socketPath = "/tmp/rust_socket" + +func makeKey(key []byte) []byte { + return key +} + +func BenchmarkSimple(b *testing.B) { + conn, err := net.Dial("unix", socketPath) + require.NoError(b, err) + defer conn.Close() + + { + req := &nomt.Request{ + Request: &nomt.Request_Update{ + Update: &nomt.UpdateRequest{ + Items: []*nomt.UpdateRequestItem{ + { + Key: makeKey([]byte("key")), + Value: []byte("value1"), + }, + }, + }, + }, + } + + resp, err := response(conn, req) + require.NoError(b, err) + b.Logf("Response: %x", resp.GetUpdate().Root) + } + + batchSize := 20 + batch := make([]*nomt.UpdateRequestItem, batchSize) + lastKey := 0 + b.Logf("Starting benchmark") + b.ResetTimer() + + for i := 0; i < b.N; i++ { + b.StopTimer() + for j := 0; j < batchSize; j++ { + batch[j] = &nomt.UpdateRequestItem{ + Key: makeKey([]byte(fmt.Sprintf("key%08d", lastKey))), + Value: []byte(fmt.Sprintf("value%d", lastKey)), + } + lastKey++ + } + req := &nomt.Request{Request: &nomt.Request_Update{Update: &nomt.UpdateRequest{Items: batch}}} + b.StartTimer() + + resp, err := response(conn, req) + require.NoError(b, err) + require.NotNil(b, resp.GetUpdate().Root) + } +} From cc48c830cbc48ddef5ab53bf3959516afd9ebc61 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 20:39:52 -0800 Subject: [PATCH 162/307] remove verbose logs --- shim/kv_backend.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shim/kv_backend.go b/shim/kv_backend.go index 8e2a3fb463..fc8f1cb36a 100644 --- a/shim/kv_backend.go +++ b/shim/kv_backend.go @@ -29,12 +29,12 @@ type KVTrieBackend struct { } func (k *KVTrieBackend) Get(key []byte) ([]byte, error) { - fmt.Printf("Get: %x\n", key) + //fmt.Printf("Get: %x\n", key) return k.backend.Get(key) } func (k *KVTrieBackend) Prefetch(key []byte) ([]byte, error) { - fmt.Printf("Prefetch: %x\n", key) + //fmt.Printf("Prefetch: %x\n", key) return k.backend.Get(key) } @@ -42,10 +42,10 @@ func (k *KVTrieBackend) Hash(batch Batch) common.Hash { if k.hashed { return k.hash } - fmt.Printf("Update Total: %d\n", len(batch)) - for _, kv := range batch { - fmt.Printf("Update: %x %x\n", kv.Key, kv.Value) - } + //fmt.Printf("Update Total: %d\n", len(batch)) + //for _, kv := range batch { + // fmt.Printf("Update: %x %x\n", kv.Key, kv.Value) + //} root, err := k.backend.Update(batch) if err != nil { panic(fmt.Sprintf("failed to update trie: %v", err)) From 0c3425d130cfde1bcd285790a5bcd1cd1a488fd6 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 1 Jan 2025 20:41:30 -0800 Subject: [PATCH 163/307] logEach for reprocess --- plugin/evm/reprocess_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c88ab2bf11..1e6e9aa95d 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -427,7 +427,9 @@ func reprocess( tapeRecorder.WriteToDisk(block, uint16(len(atomicTxs))) tapeRecorder.Reset() } else { - t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) + if i%uint64(logEach) == 0 { + t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) + } } // t.Logf("Accepting block %d, was inserted with root: %x, hash: %x", i, lastInsertedRoot, block.Hash()) From 92e4e2c0eea0afab3d3f9d11327d4b7b327003c8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 13:15:41 -0800 Subject: [PATCH 164/307] change --- plugin/evm/reprocess_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 1e6e9aa95d..e69e572a36 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -297,12 +297,9 @@ func TestReprocessMainnetBlocks(t *testing.T) { startBlock++ } - for _, backend := range []*reprocessBackend{ - getMainnetBackend(t, "nomt", source, dbs), - getMainnetBackend(t, "merkledb", source, dbs), - getMainnetBackend(t, "legacy", source, dbs), - } { - t.Run(backend.Name, func(t *testing.T) { + for _, backendName := range []string{"nomt", "merkledb", "legacy"} { + t.Run(backendName, func(t *testing.T) { + backend := getMainnetBackend(t, backendName, source, dbs) lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) t.Logf("Last hash: %x, Last root: %x", lastHash, lastRoot) }) From 166b52841ed902fed9597e6fd33bd207cda7ba3d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 17:29:08 -0800 Subject: [PATCH 165/307] add writes from cache evicts --- core/blockchain.go | 4 + plugin/evm/post_processing_test.go | 161 ++++++++++++++++++++++----- plugin/evm/reprocess_backend_test.go | 26 ++--- plugin/evm/reprocess_test.go | 6 + shim/kv_backend.go | 2 +- shim/legacy/legacy.go | 104 +++++++++++++++++ triedb/database.go | 2 + 7 files changed, 262 insertions(+), 43 deletions(-) create mode 100644 shim/legacy/legacy.go diff --git a/core/blockchain.go b/core/blockchain.go index 59cb8a108c..98867346bb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -185,6 +185,10 @@ type CacheConfig struct { SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } +func (c *CacheConfig) TrieDBConfig() *triedb.Config { + return c.triedbConfig() +} + // triedbConfig derives the configures for trie database. func (c *CacheConfig) triedbConfig() *triedb.Config { config := &triedb.Config{Preimages: c.Preimages} diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 7ee3829886..9392dcc738 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -4,10 +4,14 @@ import ( "fmt" "io" "testing" + "time" "github.com/VictoriaMetrics/fastcache" "github.com/Yiling-J/theine-go" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/coreth/shim/legacy" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" lru "github.com/hashicorp/golang-lru/v2" "github.com/maypok86/otter" "github.com/stretchr/testify/require" @@ -36,10 +40,19 @@ type totals struct { storageWriteHits uint64 writeCacheEvictAccount uint64 writeCacheEvictStorage uint64 + + // eviction (historical state storage update) time + writeCacheEvictTime uint64 + + // update time (historical state state commitment + persistence) + storageUpdateTime uint64 + storageUpdateCount uint64 + storagePersistTime uint64 + storagePersistCount uint64 } type cacheIntf interface { - Get(k string, v []byte) bool + GetAndSet(k string, v []byte) bool Delete(k string) Len() int EstimatedSize() int @@ -49,7 +62,7 @@ type fastCache struct { cache *fastcache.Cache } -func (c *fastCache) Get(k string, v []byte) bool { +func (c *fastCache) GetAndSet(k string, v []byte) bool { found := c.cache.Has([]byte(k)) c.cache.Set([]byte(k), v) return found @@ -75,7 +88,7 @@ type theineCache struct { *theine.Cache[string, []byte] } -func (c *theineCache) Get(k string, v []byte) bool { +func (c *theineCache) GetAndSet(k string, v []byte) bool { _, found := c.Cache.Get(k) c.Cache.Set(k, v, int64(len(k)+len(v))) return found @@ -85,7 +98,7 @@ type otterCache struct { otter.Cache[string, []byte] } -func (c *otterCache) Get(k string, v []byte) bool { +func (c *otterCache) GetAndSet(k string, v []byte) bool { _, found := c.Cache.Get(k) c.Cache.Set(k, v) return found @@ -99,21 +112,34 @@ func (c *otterCache) Len() int { return c.Cache.Size() } -type noCache struct{} +type noCache[K, V any] struct { + onEvict func(k K, v V) +} -func (c *noCache) Get(k string, v []byte) bool { return false } -func (c *noCache) Delete(k string) {} -func (c *noCache) Len() int { return 0 } -func (c *noCache) EstimatedSize() int { return 0 } +func (c *noCache[K, V]) Get(k K) (v V, ok bool) { return } +func (c *noCache[K, V]) GetAndSet(k K, v V) bool { return false } +func (c *noCache[K, V]) Delete(k K) {} +func (c *noCache[K, V]) Len() int { return 0 } +func (c *noCache[K, V]) EstimatedSize() int { return 0 } +func (c *noCache[K, V]) GetOldest() (k K, v V, ok bool) { return } + +func (c *noCache[K, V]) Add(k K, v V) bool { + if c.onEvict != nil { + c.onEvict(k, v) + } + return false +} type withUpdatedAt struct { val []byte updatedAt uint64 } -func short(s string) string { - // return first 2 and last 2 characters - return s[:2] + "..." + s[len(s)-2:] +type writeCache[K, V any] interface { + Get(k K) (V, bool) + Add(k K, v V) bool + GetOldest() (K, V, bool) + Len() int } func TestPostProcess(t *testing.T) { @@ -144,28 +170,71 @@ func TestPostProcess(t *testing.T) { } cache = &otterCache{Cache: impl} } else if readCacheBackend == "none" { - cache = &noCache{} + cache = &noCache[string, []byte]{} } else { t.Fatalf("Unknown cache backend: %s", readCacheBackend) } - var writeCache *lru.Cache[string, withUpdatedAt] - var sum totals - var blockNumber uint64 + var ( + sum totals + blockNumber uint64 + storageRoot common.Hash + storage triedb.KVBackend + evitcedBatch triedb.Batch + lastCommit struct { + txs uint64 + number uint64 + } + ) + if storageBackend != "none" { + dbs := openDBs(t) + defer dbs.Close() + + lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) + t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) + + if usePersistedStartBlock { + startBlock = lastHeight + } + require.Equal(t, lastHeight, startBlock, "Last height does not match start block") + + storage = getKVBackend(t, storageBackend, dbs.merkledb) + if storageBackend == "legacy" { + cacheConfig := getCacheConfig(t, storageBackend, storage) + tdbConfig := cacheConfig.TrieDBConfig() + tdb := triedb.NewDatabase(dbs.chain, tdbConfig) + storage = legacy.New(tdb, lastRoot, lastHeight, true) + } + require.Equal(t, lastRoot, storage.Root(), "Root mismatch") + storageRoot = lastRoot + t.Logf("Storage backend initialized: %s", storageBackend) + } + hst := histogram.NewFast() hstWithReset := histogram.NewFast() inf := float64(1_000_000_000) onEvict := func(k string, v withUpdatedAt) { + now := time.Now() if len(k) == 32 { sum.writeCacheEvictAccount++ } else { sum.writeCacheEvictStorage++ } // t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) + if storage != nil { + evitcedBatch = append(evitcedBatch, triedb.KV{Key: []byte(k), Value: v.val}) + } + sum.writeCacheEvictTime += uint64(time.Since(now).Nanoseconds()) } - writeCache, err := lru.NewWithEvict(int(writeCacheSize), onEvict) - require.NoError(t, err) + var writeCache writeCache[string, withUpdatedAt] = &noCache[string, withUpdatedAt]{ + onEvict: onEvict, + } + if writeCacheSize > 0 { + var err error + writeCache, err = lru.NewWithEvict(int(writeCacheSize), onEvict) + require.NoError(t, err) + } fm := &fileManager{dir: tapeDir, newEach: 10_000} @@ -191,7 +260,7 @@ func TestPostProcess(t *testing.T) { accountReads: make(map[string][]byte), storageReads: make(map[string][]byte), } - tapeTxs := processTape(t, r, tapeResult, cache.Get, &sum) + tapeTxs := processTape(t, r, tapeResult, cache.GetAndSet, &sum) require.Equal(t, txs, tapeTxs) accountWrites, err := readUint16(r) @@ -265,6 +334,31 @@ func TestPostProcess(t *testing.T) { sum.blocks++ sum.txs += uint64(txs) sum.atomicTxs += uint64(atomicTxs) + + if storage != nil { + shouldCommitBlocks := commitEachBlocks > 0 && blockNumber-lastCommit.number >= uint64(commitEachBlocks) + shouldCommitTxs := commitEachTxs > 0 && sum.txs+sum.atomicTxs-lastCommit.txs >= uint64(commitEachTxs) + if len(evitcedBatch) > 0 && (shouldCommitBlocks || shouldCommitTxs) { + now := time.Now() + // Get state commitment from storage backend + storageRoot, err = storage.Update(evitcedBatch) + require.NoError(t, err) + evitcedBatch = evitcedBatch[:0] + updateTime := uint64(time.Since(now).Nanoseconds()) + + // Request storage backend to persist the state + err = storage.Commit(storageRoot) + require.NoError(t, err) + + sum.storagePersistTime += uint64(time.Since(now).Nanoseconds()) - updateTime + sum.storageUpdateTime += updateTime + sum.storagePersistCount++ + sum.storageUpdateCount++ + } + lastCommit.number = blockNumber + lastCommit.txs = sum.txs + sum.atomicTxs + } + sum.accountReads += uint64(len(tapeResult.accountReads)) sum.storageReads += uint64(len(tapeResult.storageReads)) sum.accountWrites += uint64(accountWrites) @@ -277,15 +371,21 @@ func TestPostProcess(t *testing.T) { sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) if blockNumber%uint64(logEach) == 0 { - t.Logf("Block[%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", - blockHash.TerminalString(), blockNumber, - sum.txs, sum.atomicTxs, - sum.accountReads, sum.storageReads, - sum.accountReadHits, sum.storageReadHits, - sum.accountWrites, sum.storageWrites, - sum.accountWriteHits, sum.storageWriteHits, - sum.accountUpdates, sum.storageUpdates, sum.accountDeletes, sum.storageDeletes, - sum.accounts, sum.storage) + storageRootStr := "" + if storageRoot != (common.Hash{}) { + storageRootStr = "/" + storageRoot.TerminalString() + } + t.Logf("Block[%s%s]: (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)", + blockHash.TerminalString(), storageRootStr, blockNumber, + sum.txs-lastReported.txs, sum.atomicTxs-lastReported.atomicTxs, + sum.accountReads-lastReported.accountReads, sum.storageReads-lastReported.storageReads, + sum.accountReadHits-lastReported.accountReadHits, sum.storageReadHits-lastReported.storageReadHits, + sum.accountWrites-lastReported.accountWrites, sum.storageWrites-lastReported.storageWrites, + sum.accountWriteHits-lastReported.accountWriteHits, sum.storageWriteHits-lastReported.storageWriteHits, + sum.accountUpdates-lastReported.accountUpdates, sum.storageUpdates-lastReported.storageUpdates, + sum.accountDeletes-lastReported.accountDeletes, sum.storageDeletes-lastReported.storageDeletes, + sum.accounts-lastReported.accounts, sum.storage-lastReported.storage, + ) if readCacheBackend != "none" { hits := sum.accountReadHits - lastReported.accountReadHits + sum.storageReadHits - lastReported.storageReadHits total := sum.accountReads - lastReported.accountReads + sum.storageReads - lastReported.storageReads @@ -301,12 +401,15 @@ func TestPostProcess(t *testing.T) { _, oldest, _ := writeCache.GetOldest() txs := sum.txs - lastReported.txs t.Logf( - "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (oldest age: %d)", + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (time: %d, total: %d micros) (updates: %d, time: %d, total %d micros) (commits: %d, time: %d, total %d micros) (oldest age: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), float64(sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount)/float64(txs), float64(sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage)/float64(txs), (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, + (sum.writeCacheEvictTime-lastReported.writeCacheEvictTime)/1000, sum.writeCacheEvictTime/1000, + sum.storageUpdateCount-lastReported.storageUpdateCount, (sum.storageUpdateTime-lastReported.storageUpdateTime)/1000, sum.storageUpdateTime/1000, + sum.storagePersistCount-lastReported.storagePersistCount, (sum.storagePersistTime-lastReported.storagePersistTime)/1000, sum.storagePersistTime/1000, blockNumber-oldest.updatedAt, ) quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95} diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 76d6f7ceaa..0d5b5b69dc 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -184,19 +184,7 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs cbs := dummy.ConsensusCallbacks{OnExtraStateChange: testVM.onExtraStateChange} engine := dummy.NewFakerWithMode(cbs, dummy.Mode{ModeSkipHeader: true}) - var ( - merkleDB xmerkledb.MerkleDB - kvBackend triedb.KVBackend - ) - if name == "merkledb" { - merkleDB = getMerkleDB(t, dbs.merkledb) - kvBackend = merkledb.NewMerkleDB(merkleDB) - } - if name == "nomt" { - conn, err := net.Dial("unix", socketPath) - require.NoError(t, err) - kvBackend = nomt.New(conn) - } + kvBackend := getKVBackend(t, name, dbs.merkledb) return &reprocessBackend{ Genesis: &g, Engine: engine, @@ -213,3 +201,15 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs VerifyRoot: name == "legacy", } } + +func getKVBackend(t *testing.T, name string, merkleKVStore database.Database) triedb.KVBackend { + if name == "merkledb" { + return merkledb.NewMerkleDB(getMerkleDB(t, merkleKVStore)) + } + if name == "nomt" { + conn, err := net.Dial("unix", socketPath) + require.NoError(t, err) + return nomt.New(conn) + } + return nil +} diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index e69e572a36..525c19baf6 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -50,6 +50,9 @@ var ( readCacheSize = int64(256) readCacheBackend = "none" writeCacheSize = uint64(1024) + storageBackend = "none" + commitEachBlocks = 1 + commitEachTxs = 0 // merkledb options merkleDBBranchFactor = 16 @@ -82,6 +85,9 @@ func TestMain(m *testing.M) { flag.StringVar(&readCacheBackend, "readCacheBackend", readCacheBackend, "read cache backend (theine, fastcache, otter, none)") flag.Uint64Var(&writeCacheSize, "writeCacheSize", writeCacheSize, "write cache size in items") flag.StringVar(&socketPath, "socketPath", socketPath, "socket path") + flag.StringVar(&storageBackend, "storageBackend", storageBackend, "storage backend (none, legacy, merkledb, nomt)") + flag.IntVar(&commitEachBlocks, "commitEachBlocks", commitEachBlocks, "commit each N blocks") + flag.IntVar(&commitEachTxs, "commitEachTxs", commitEachTxs, "commit each N transactions") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") diff --git a/shim/kv_backend.go b/shim/kv_backend.go index fc8f1cb36a..c089d1d037 100644 --- a/shim/kv_backend.go +++ b/shim/kv_backend.go @@ -35,7 +35,7 @@ func (k *KVTrieBackend) Get(key []byte) ([]byte, error) { func (k *KVTrieBackend) Prefetch(key []byte) ([]byte, error) { //fmt.Printf("Prefetch: %x\n", key) - return k.backend.Get(key) + return k.backend.Prefetch(key) } func (k *KVTrieBackend) Hash(batch Batch) common.Hash { diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go new file mode 100644 index 0000000000..2b8ab5c973 --- /dev/null +++ b/shim/legacy/legacy.go @@ -0,0 +1,104 @@ +package legacy + +import ( + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/trie" + "github.com/ava-labs/coreth/trie/trienode" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" +) + +var _ triedb.KVBackend = &Legacy{} + +type Legacy struct { + triedb *triedb.Database + root common.Hash + count uint64 + dereference bool +} + +func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bool) *Legacy { + return &Legacy{ + triedb: triedb, + root: root, + count: count, + dereference: dereference, + } +} + +func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { + accounts, err := trie.NewStateTrie(trie.StateTrieID(l.root), l.triedb) + if err != nil { + return common.Hash{}, err + } + // Process the storage tries first, this means we can access the root for the + // storage tries before they are updated in the account trie. Necessary for + // the hash scheme. + tries := make(map[common.Hash]*trie.StateTrie) + for _, kv := range batch { + if len(kv.Key) != 64 { + continue + } + accHash := common.BytesToHash(kv.Key[:32]) + acc, err := accounts.GetAccountByHash(accHash) + if err != nil { + return common.Hash{}, err + } + root := types.EmptyRootHash + if acc != nil { + root = acc.Root + } + tr, ok := tries[accHash] + if !ok { + legacyTrie, err := trie.NewStateTrie(trie.StorageTrieID(l.root, accHash, root), l.triedb) + if err != nil { + } + tries[accHash] = legacyTrie + } + + // Update the storage trie + tr.MustUpdate(kv.Key[32:], kv.Value) + } + + // Hash the storage tries + nodes := trienode.NewMergedNodeSet() + for _, tr := range tries { + _, set, err := tr.Commit(false) + if err != nil { + return common.Hash{}, err + } + nodes.Merge(set) + } + + // Update the account trie + for _, kv := range batch { + if len(kv.Key) == 64 { + continue + } + accounts.MustUpdate(kv.Key, kv.Value) + } + next, set, err := accounts.Commit(false) + if err != nil { + return common.Hash{}, err + } + nodes.Merge(set) + + if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { + return common.Hash{}, err + } + + if l.dereference { + if err := l.triedb.Dereference(l.root); err != nil { + return common.Hash{}, err + } + } + l.root = next + l.count++ + return next, nil +} + +func (l *Legacy) Commit(root common.Hash) error { return l.triedb.Commit(root, false) } +func (l *Legacy) Close() error { return nil } +func (l *Legacy) Get(key []byte) ([]byte, error) { panic("implement me") } +func (l *Legacy) Prefetch(key []byte) ([]byte, error) { panic("implement me") } +func (l *Legacy) Root() common.Hash { return l.root } diff --git a/triedb/database.go b/triedb/database.go index ffc2d64bcc..9cb7405aec 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -52,6 +52,8 @@ type KVBackend interface { // After this call, Root() should return the same hash as returned by this call. // Note when len(Value) == 0, it means the key should be deleted. + // Note after this call, the next call to Update must build on the returned root, + // regardless of whether Commit is called. Update(Batch) (common.Hash, error) // After this call, changes related to [root] should be persisted to disk. From 4efc8c8082ed2f7a3c1fdb4832ce39a9cd36a4ec Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 17:45:20 -0800 Subject: [PATCH 166/307] try --- shim/legacy/legacy.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 2b8ab5c973..26bb5e52c5 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -87,11 +87,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { return common.Hash{}, err } - if l.dereference { - if err := l.triedb.Dereference(l.root); err != nil { - return common.Hash{}, err - } - } + // TODO: fix hashdb scheme later l.root = next l.count++ return next, nil From 40d9078b6b1beff26088e5f24ef2e20697c5cc4f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 17:46:18 -0800 Subject: [PATCH 167/307] fix --- shim/legacy/legacy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 26bb5e52c5..427c5a426b 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -50,10 +50,10 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } tr, ok := tries[accHash] if !ok { - legacyTrie, err := trie.NewStateTrie(trie.StorageTrieID(l.root, accHash, root), l.triedb) + tr, err = trie.NewStateTrie(trie.StorageTrieID(l.root, accHash, root), l.triedb) if err != nil { } - tries[accHash] = legacyTrie + tries[accHash] = tr } // Update the storage trie From cdec9603fb03bce92292acb692659a8596eec907 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 17:54:08 -0800 Subject: [PATCH 168/307] track metadata --- plugin/evm/post_processing_test.go | 5 ++++- plugin/evm/reprocess_test.go | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 9392dcc738..94bbfb4195 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -176,6 +176,7 @@ func TestPostProcess(t *testing.T) { } var ( + dbs dbs sum totals blockNumber uint64 storageRoot common.Hash @@ -187,7 +188,7 @@ func TestPostProcess(t *testing.T) { } ) if storageBackend != "none" { - dbs := openDBs(t) + dbs = openDBs(t) defer dbs.Close() lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) @@ -354,6 +355,8 @@ func TestPostProcess(t *testing.T) { sum.storageUpdateTime += updateTime sum.storagePersistCount++ sum.storageUpdateCount++ + + updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) } lastCommit.number = blockNumber lastCommit.txs = sum.txs + sum.atomicTxs diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 525c19baf6..e781aa6097 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -445,16 +445,19 @@ func reprocess( bc.DrainAcceptorQueue() - // Update metadata - require.NoError(t, backend.Metadata.Put(lastAcceptedRootKey, lastRoot.Bytes())) - require.NoError(t, backend.Metadata.Put(lastAcceptedHashKey, lastHash.Bytes())) - require.NoError(t, database.PutUInt64(backend.Metadata, lastAcceptedHeightKey, i)) + updateMetadata(t, backend.Metadata, lastHash, lastRoot, i) lock.Unlock() } return lastHash, lastRoot } +func updateMetadata(t *testing.T, db database.Database, lastHash, lastRoot common.Hash, lastHeight uint64) { + require.NoError(t, db.Put(lastAcceptedRootKey, lastRoot.Bytes())) + require.NoError(t, db.Put(lastAcceptedHashKey, lastHash.Bytes())) + require.NoError(t, database.PutUInt64(db, lastAcceptedHeightKey, lastHeight)) +} + func TestCheckSnapshot(t *testing.T) { dbs := openDBs(t) defer dbs.Close() From e2fd0a59109fb855c5af296574f8f1246bf09650 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 17:57:32 -0800 Subject: [PATCH 169/307] fix start --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 94bbfb4195..cefca129b0 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -195,9 +195,9 @@ func TestPostProcess(t *testing.T) { t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) if usePersistedStartBlock { - startBlock = lastHeight + start = lastHeight } - require.Equal(t, lastHeight, startBlock, "Last height does not match start block") + require.Equal(t, lastHeight, start, "Last height does not match start block") storage = getKVBackend(t, storageBackend, dbs.merkledb) if storageBackend == "legacy" { From f8376468019a3dff510df8538409aeff08dfdfc3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:05:36 -0800 Subject: [PATCH 170/307] skip blocks --- plugin/evm/post_processing_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index cefca129b0..c89bd6011c 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -246,7 +246,7 @@ func TestPostProcess(t *testing.T) { var err error blockNumber, err = readUint64(r) require.NoError(t, err) - require.Equal(t, i, blockNumber) + require.LessOrEqual(t, blockNumber, i) blockHash, err := readHash(r) require.NoError(t, err) @@ -270,6 +270,21 @@ func TestPostProcess(t *testing.T) { storageWrites, err := readUint16(r) require.NoError(t, err) + if blockNumber < i { + // we need to just finish reading the block but not process it + for j := 0; j < int(accountWrites); j++ { + _, _, err := readKV(r, 32) + require.NoError(t, err) + } + for j := 0; j < int(storageWrites); j++ { + _, _, err := readKV(r, 64) + require.NoError(t, err) + } + t.Logf("Skipping block %d", blockNumber) + i-- + continue + } + accountUpdates, storageUpdates := 0, 0 accountDeletes, storageDeletes := 0, 0 From ff6fc886918768611b29067cdd9790634e211341 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:07:51 -0800 Subject: [PATCH 171/307] try --- shim/legacy/legacy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 427c5a426b..3b2470d85a 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -77,7 +77,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } accounts.MustUpdate(kv.Key, kv.Value) } - next, set, err := accounts.Commit(false) + next, set, err := accounts.Commit(true) if err != nil { return common.Hash{}, err } From 666d506acbf83074ab4b9c23ceca3b4279918d7a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:09:09 -0800 Subject: [PATCH 172/307] try --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index c89bd6011c..0e148b15f4 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -195,9 +195,9 @@ func TestPostProcess(t *testing.T) { t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) if usePersistedStartBlock { - start = lastHeight + start = lastHeight + 1 } - require.Equal(t, lastHeight, start, "Last height does not match start block") + require.Equal(t, lastHeight+1, start, "Last height does not match start block") storage = getKVBackend(t, storageBackend, dbs.merkledb) if storageBackend == "legacy" { From 5edea8524832b7a5ff4822784eb8f8a93adb1b8c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:11:22 -0800 Subject: [PATCH 173/307] improve skip log --- plugin/evm/post_processing_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 0e148b15f4..e108cd68c1 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -280,7 +280,9 @@ func TestPostProcess(t *testing.T) { _, _, err := readKV(r, 64) require.NoError(t, err) } - t.Logf("Skipping block %d", blockNumber) + if blockNumber%uint64(logEach) == 0 { + t.Logf("Skipping block %d", blockNumber) + } i-- continue } From 0f9613353bd057ce12bd81c54ea9ba371e1787a9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:15:11 -0800 Subject: [PATCH 174/307] try --- plugin/evm/post_processing_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index e108cd68c1..7acab9763f 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -193,6 +193,7 @@ func TestPostProcess(t *testing.T) { lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) + lastCommit.number = lastHeight if usePersistedStartBlock { start = lastHeight + 1 From cb7bdb50fe4e36b4f23b69c92dd8cb10cc6bd195 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:16:40 -0800 Subject: [PATCH 175/307] fix --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 7acab9763f..6ff3b5fc86 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -375,9 +375,9 @@ func TestPostProcess(t *testing.T) { sum.storageUpdateCount++ updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) + lastCommit.number = blockNumber + lastCommit.txs = sum.txs + sum.atomicTxs } - lastCommit.number = blockNumber - lastCommit.txs = sum.txs + sum.atomicTxs } sum.accountReads += uint64(len(tapeResult.accountReads)) From 235a531533999dff58c373667e7e90a242863a53 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:37:16 -0800 Subject: [PATCH 176/307] try --- core/blockchain.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index 98867346bb..033379db6c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -603,6 +603,10 @@ func (bc *BlockChain) startAcceptor() { acceptorQueueGauge.Dec(1) if err := bc.flattenSnapshot(func() error { + parent := bc.GetHeaderByHash(next.Hash()) + if parent.Root == next.Block.Root() { + return nil + } return bc.stateManager.AcceptTrie(next) }, next.Hash()); err != nil { log.Crit("unable to flatten snapshot from acceptor", "blockHash", next.Hash(), "err", err) From e87059f0daabd9cb8894691caad2e5d43ebbd1b5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:51:33 -0800 Subject: [PATCH 177/307] add TestQueryBlock --- plugin/evm/reprocess_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index e781aa6097..616df0eabd 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -165,6 +165,21 @@ func TestExportBlocks(t *testing.T) { t.Logf("Exported %d blocks", endBlock-startBlock+1) } +func TestQueryBlock(t *testing.T) { + sourceDb := openSourceDB(t) + defer sourceDb.Close() + + for i := startBlock; i <= endBlock; i++ { + hash := rawdb.ReadCanonicalHash(sourceDb, i) + block := rawdb.ReadBlock(sourceDb, hash, i) + if block == nil { + t.Fatalf("Block %d not found", i) + } + + t.Logf("Block %d: %x, %x", i, hash, block.Root()) + } +} + var ( VMDBPrefix = []byte("vm") fujiXChainID = ids.FromStringOrPanic("2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm") From 7849e57255c381039f89bc62ddb5cc09b3f78d3d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 18:57:34 -0800 Subject: [PATCH 178/307] try --- shim/trie.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shim/trie.go b/shim/trie.go index a99cadd338..6a68719124 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -89,6 +89,8 @@ func (t *Trie) Copy() *Trie { if legacy, ok := t.backend.(*LegacyBackend); ok { return &Trie{ backend: &LegacyBackend{tr: legacy.tr.Copy()}, + prefix: t.prefix, + parent: t.parent, } } // fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) From 61121ad29bbdee5d0fcd2a19283e89465f703a2f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:05:31 -0800 Subject: [PATCH 179/307] try --- core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 033379db6c..040ca1ccf0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -603,7 +603,7 @@ func (bc *BlockChain) startAcceptor() { acceptorQueueGauge.Dec(1) if err := bc.flattenSnapshot(func() error { - parent := bc.GetHeaderByHash(next.Hash()) + parent := bc.GetHeaderByHash(next.ParentHash()) if parent.Root == next.Block.Root() { return nil } From 9fb65dc35c4303ebed56d35188e6f2a4f1f47e01 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:08:30 -0800 Subject: [PATCH 180/307] try --- shim/trie.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shim/trie.go b/shim/trie.go index 6a68719124..8446b7f024 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -91,6 +91,8 @@ func (t *Trie) Copy() *Trie { backend: &LegacyBackend{tr: legacy.tr.Copy()}, prefix: t.prefix, parent: t.parent, + origin: t.origin, + changes: append(Batch(nil), t.changes...), } } // fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) From 6c99c1978b3c58c354c1e8fb0bbea4f6c43409f7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:10:53 -0800 Subject: [PATCH 181/307] try --- shim/trie.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shim/trie.go b/shim/trie.go index 8446b7f024..e0a150617e 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -88,7 +88,11 @@ func (t *Trie) Prefetch(key []byte) ([]byte, error) { func (t *Trie) Copy() *Trie { if legacy, ok := t.backend.(*LegacyBackend); ok { return &Trie{ - backend: &LegacyBackend{tr: legacy.tr.Copy()}, + backend: &LegacyBackend{ + tr: legacy.tr.Copy(), + addrHash: legacy.addrHash, + writer: legacy.writer, + }, prefix: t.prefix, parent: t.parent, origin: t.origin, From afce6e4e6a7efe0b09d57cc1d77b8067077c161d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:19:52 -0800 Subject: [PATCH 182/307] fix --- go.sum | 2 -- plugin/evm/reprocess_backend_test.go | 2 -- plugin/evm/reprocess_test.go | 3 ++- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/go.sum b/go.sum index da27a46d67..3703a8a15b 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.12.2-0.20241224161435-3998475d671d h1:QCtjS4ANcNfCdL6Z2sKpanDVJNt1MU0bUyVdW0g5zuU= -github.com/ava-labs/avalanchego v1.12.2-0.20241224161435-3998475d671d/go.mod h1:cDoT0Hq3P+/XfCyVvzrBj66yoid2I5LnMuj7LIkap+o= github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d h1:iPlsqC9pIy4emCo8wyI/VmVmfljpzmw58ZqahVdcehI= github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d/go.mod h1:dKawab3nXqwI7ZcOFatTOv//l1V0t8MRBnhXoOqbN4E= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 0d5b5b69dc..3dd0099b11 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -91,7 +91,6 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB } testVM := &VM{ chainConfig: chainConfig, - codec: Codec, ctx: &snow.Context{AVAXAssetID: ids.ID{1}}, } someAddr := common.Address{1} @@ -178,7 +177,6 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs testVM := &VM{ chainConfig: g.Config, - codec: Codec, ctx: g.Config.SnowCtx, } cbs := dummy.ConsensusCallbacks{OnExtraStateChange: testVM.onExtraStateChange} diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 616df0eabd..e007fc0251 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/vm" + "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -428,7 +429,7 @@ func reprocess( for i := start; i <= stop; i++ { block := backend.GetBlock(i) isApricotPhase5 := backend.Genesis.Config.IsApricotPhase5(block.Time()) - atomicTxs, err := ExtractAtomicTxs(block.ExtData(), isApricotPhase5, Codec) + atomicTxs, err := atomic.ExtractAtomicTxs(block.ExtData(), isApricotPhase5, atomic.Codec) require.NoError(t, err) // Override parentRoot to match last state From 03838e20f7492eaf3d8894ea2e2078898a6a2888 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:23:26 -0800 Subject: [PATCH 183/307] check genesis --- plugin/evm/reprocess_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index e007fc0251..21fa666657 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -417,6 +417,9 @@ func reprocess( } t.Logf("Genesis performed: hash: %x, root : %x", bc.CurrentBlock().Hash(), lastRoot) + if tapeRecorder != nil { + t.Logf("Accounts: %d, Storages: %d", len(tapeRecorder.accountWrites), len(tapeRecorder.storageWrites)) + } start = 1 } From 9c3c69afbff33dc2d34e37c8a3333eb837a2113e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:33:04 -0800 Subject: [PATCH 184/307] verbose --- plugin/evm/post_processing_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 6ff3b5fc86..08e6060f9e 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -358,6 +358,11 @@ func TestPostProcess(t *testing.T) { shouldCommitBlocks := commitEachBlocks > 0 && blockNumber-lastCommit.number >= uint64(commitEachBlocks) shouldCommitTxs := commitEachTxs > 0 && sum.txs+sum.atomicTxs-lastCommit.txs >= uint64(commitEachTxs) if len(evitcedBatch) > 0 && (shouldCommitBlocks || shouldCommitTxs) { + if tapeVerbose { + for _, kv := range evitcedBatch { + t.Logf("storing: %x -> %x", kv.Key, kv.Value) + } + } now := time.Now() // Get state commitment from storage backend storageRoot, err = storage.Update(evitcedBatch) From 1655aa9db480e015db9ca2602e41ada89a6f0e0f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:41:33 -0800 Subject: [PATCH 185/307] try --- plugin/evm/post_processing_test.go | 2 +- shim/legacy/legacy.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 08e6060f9e..64d41f27a2 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -425,7 +425,7 @@ func TestPostProcess(t *testing.T) { writeHits := sum.accountWriteHits + sum.storageWriteHits - lastReported.accountWriteHits - lastReported.storageWriteHits writeTotal := sum.accountWrites + sum.storageWrites - lastReported.accountWrites - lastReported.storageWrites _, oldest, _ := writeCache.GetOldest() - txs := sum.txs - lastReported.txs + txs := sum.txs + sum.atomicTxs - lastReported.txs - lastReported.atomicTxs t.Logf( "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (time: %d, total: %d micros) (updates: %d, time: %d, total %d micros) (commits: %d, time: %d, total %d micros) (oldest age: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 3b2470d85a..e6983904ab 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -1,6 +1,8 @@ package legacy import ( + "fmt" + "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -75,6 +77,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if len(kv.Key) == 64 { continue } + fmt.Println("Account Update", kv.Key, kv.Value) accounts.MustUpdate(kv.Key, kv.Value) } next, set, err := accounts.Commit(true) From 12bed94ee022b55ab11bc6fb95a0dc493706c04f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:43:11 -0800 Subject: [PATCH 186/307] try --- shim/legacy/legacy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index e6983904ab..6c04099fde 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -77,7 +77,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if len(kv.Key) == 64 { continue } - fmt.Println("Account Update", kv.Key, kv.Value) + fmt.Printf("Account Update: %x, %x\n", kv.Key, kv.Value) accounts.MustUpdate(kv.Key, kv.Value) } next, set, err := accounts.Commit(true) @@ -89,6 +89,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { return common.Hash{}, err } + fmt.Printf("Legacy Update: %x\n", next) // TODO: fix hashdb scheme later l.root = next From 860b5906a7cf6208b960a8514b3a32cb1a04c6dc Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:46:46 -0800 Subject: [PATCH 187/307] log genesis --- plugin/evm/reprocess_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 21fa666657..d18e02ce2e 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -419,6 +419,14 @@ func reprocess( t.Logf("Genesis performed: hash: %x, root : %x", bc.CurrentBlock().Hash(), lastRoot) if tapeRecorder != nil { t.Logf("Accounts: %d, Storages: %d", len(tapeRecorder.accountWrites), len(tapeRecorder.storageWrites)) + if tapeVerbose { + for _, kv := range tapeRecorder.accountWrites { + t.Logf("Account: %x, %x", kv.Key, kv.Value) + } + for _, kv := range tapeRecorder.storageWrites { + t.Logf("Storage: %x, %x", kv.Key, kv.Value) + } + } } start = 1 } From c3cacaaa884cfb33f6ff9d3c2295c70728239248 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:55:39 -0800 Subject: [PATCH 188/307] try --- shim/legacy/legacy.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 6c04099fde..96045f8337 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" ) var _ triedb.KVBackend = &Legacy{} @@ -29,30 +30,34 @@ func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bo } func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { - accounts, err := trie.NewStateTrie(trie.StateTrieID(l.root), l.triedb) + accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) if err != nil { return common.Hash{}, err } // Process the storage tries first, this means we can access the root for the // storage tries before they are updated in the account trie. Necessary for // the hash scheme. - tries := make(map[common.Hash]*trie.StateTrie) + tries := make(map[common.Hash]*trie.Trie) for _, kv := range batch { if len(kv.Key) != 64 { continue } accHash := common.BytesToHash(kv.Key[:32]) - acc, err := accounts.GetAccountByHash(accHash) + accBytes, err := accounts.Get(kv.Key[:32]) if err != nil { return common.Hash{}, err } root := types.EmptyRootHash - if acc != nil { + if accBytes != nil { + var acc types.StateAccount + if err := rlp.DecodeBytes(accBytes, &acc); err != nil { + return common.Hash{}, err + } root = acc.Root } tr, ok := tries[accHash] if !ok { - tr, err = trie.NewStateTrie(trie.StorageTrieID(l.root, accHash, root), l.triedb) + tr, err = trie.New(trie.StorageTrieID(l.root, accHash, root), l.triedb) if err != nil { } tries[accHash] = tr From 4b918c591c3c1ac1c3cc398f11ee7d337ee0ff81 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 19:57:56 -0800 Subject: [PATCH 189/307] too verbose --- shim/legacy/legacy.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 96045f8337..e80370efa9 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -1,8 +1,6 @@ package legacy import ( - "fmt" - "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -82,7 +80,6 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if len(kv.Key) == 64 { continue } - fmt.Printf("Account Update: %x, %x\n", kv.Key, kv.Value) accounts.MustUpdate(kv.Key, kv.Value) } next, set, err := accounts.Commit(true) @@ -94,7 +91,6 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { return common.Hash{}, err } - fmt.Printf("Legacy Update: %x\n", next) // TODO: fix hashdb scheme later l.root = next From 59c726c539d488341d56a90efe4d44cde2e283f7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 20:02:44 -0800 Subject: [PATCH 190/307] nit --- plugin/evm/post_processing_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 64d41f27a2..0f28d102cf 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -424,7 +424,10 @@ func TestPostProcess(t *testing.T) { } writeHits := sum.accountWriteHits + sum.storageWriteHits - lastReported.accountWriteHits - lastReported.storageWriteHits writeTotal := sum.accountWrites + sum.storageWrites - lastReported.accountWrites - lastReported.storageWrites - _, oldest, _ := writeCache.GetOldest() + _, oldest, found := writeCache.GetOldest() + if !found { + oldest.updatedAt = blockNumber // so displays as 0 + } txs := sum.txs + sum.atomicTxs - lastReported.txs - lastReported.atomicTxs t.Logf( "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (time: %d, total: %d micros) (updates: %d, time: %d, total %d micros) (commits: %d, time: %d, total %d micros) (oldest age: %d)", From 61fcaa786c1419e12066125f65b7759c1fe96a01 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 20:04:46 -0800 Subject: [PATCH 191/307] patch --- shim/legacy/legacy.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index e80370efa9..26724b2223 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -72,7 +72,9 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if err != nil { return common.Hash{}, err } - nodes.Merge(set) + if set != nil { + nodes.Merge(set) + } } // Update the account trie @@ -86,7 +88,9 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if err != nil { return common.Hash{}, err } - nodes.Merge(set) + if set != nil { + nodes.Merge(set) + } if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { return common.Hash{}, err From 849269fb6753836d220ac3d2cbb2d1332cb02b29 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 20:21:45 -0800 Subject: [PATCH 192/307] only send unique updates to underlying storage --- plugin/evm/post_processing_test.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 0f28d102cf..bd693fee5d 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -181,7 +181,7 @@ func TestPostProcess(t *testing.T) { blockNumber uint64 storageRoot common.Hash storage triedb.KVBackend - evitcedBatch triedb.Batch + evitcedBatch = make(map[string][]byte) lastCommit struct { txs uint64 number uint64 @@ -224,7 +224,7 @@ func TestPostProcess(t *testing.T) { } // t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) if storage != nil { - evitcedBatch = append(evitcedBatch, triedb.KV{Key: []byte(k), Value: v.val}) + evitcedBatch[k] = v.val } sum.writeCacheEvictTime += uint64(time.Since(now).Nanoseconds()) } @@ -359,15 +359,19 @@ func TestPostProcess(t *testing.T) { shouldCommitTxs := commitEachTxs > 0 && sum.txs+sum.atomicTxs-lastCommit.txs >= uint64(commitEachTxs) if len(evitcedBatch) > 0 && (shouldCommitBlocks || shouldCommitTxs) { if tapeVerbose { - for _, kv := range evitcedBatch { - t.Logf("storing: %x -> %x", kv.Key, kv.Value) + for k, v := range evitcedBatch { + t.Logf("storing: %x -> %x", k, v) } } + batch := make(triedb.Batch, 0, len(evitcedBatch)) + for k, v := range evitcedBatch { + batch = append(batch, triedb.KV{Key: []byte(k), Value: v}) + } now := time.Now() // Get state commitment from storage backend - storageRoot, err = storage.Update(evitcedBatch) + storageRoot, err = storage.Update(batch) require.NoError(t, err) - evitcedBatch = evitcedBatch[:0] + clear(evitcedBatch) updateTime := uint64(time.Since(now).Nanoseconds()) // Request storage backend to persist the state From 73dc9cee89be3d8eb9b49a8216f0dc209c7598c0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 20:52:29 -0800 Subject: [PATCH 193/307] formatting --- plugin/evm/post_processing_test.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index bd693fee5d..e513710980 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -45,9 +45,9 @@ type totals struct { writeCacheEvictTime uint64 // update time (historical state state commitment + persistence) - storageUpdateTime uint64 + storageUpdateTime time.Duration storageUpdateCount uint64 - storagePersistTime uint64 + storagePersistTime time.Duration storagePersistCount uint64 } @@ -372,13 +372,13 @@ func TestPostProcess(t *testing.T) { storageRoot, err = storage.Update(batch) require.NoError(t, err) clear(evitcedBatch) - updateTime := uint64(time.Since(now).Nanoseconds()) + updateTime := time.Since(now) // Request storage backend to persist the state err = storage.Commit(storageRoot) require.NoError(t, err) - sum.storagePersistTime += uint64(time.Since(now).Nanoseconds()) - updateTime + sum.storagePersistTime += time.Since(now) - updateTime sum.storageUpdateTime += updateTime sum.storagePersistCount++ sum.storageUpdateCount++ @@ -433,16 +433,28 @@ func TestPostProcess(t *testing.T) { oldest.updatedAt = blockNumber // so displays as 0 } txs := sum.txs + sum.atomicTxs - lastReported.txs - lastReported.atomicTxs + storageUpdateCount := sum.storageUpdateCount - lastReported.storageUpdateCount + storageUpdateTime := sum.storageUpdateTime - lastReported.storageUpdateTime + storageUpdateAvg := int64(0) + if storageUpdateCount > 0 { + storageUpdateAvg = storageUpdateTime.Milliseconds() / int64(storageUpdateCount) + } + storagePersistCount := sum.storagePersistCount - lastReported.storagePersistCount + storagePersistTime := sum.storagePersistTime - lastReported.storagePersistTime + storagePersistAvg := int64(0) + if storagePersistCount > 0 { + storagePersistAvg = storagePersistTime.Milliseconds() / int64(storagePersistCount) + } t.Logf( - "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (time: %d, total: %d micros) (updates: %d, time: %d, total %d micros) (commits: %d, time: %d, total %d micros) (oldest age: %d)", + "Write cache stats: %d hits, %d misses, %.2f hit rate, %d entries (= %.4f of state), evicted/tx: %.1f acc, %.1f storage (total: %dk) (time: %d, total: %d ms) (updates: %d, time: %d, avg: %d, total: %d ms) (commits: %d, time: %d, avg: %d, total %d ms) (oldest age: %d)", writeHits, writeTotal-writeHits, float64(writeHits)/float64(writeTotal), writeCache.Len(), float64(writeCache.Len())/float64(sum.accounts+sum.storage), float64(sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount)/float64(txs), float64(sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage)/float64(txs), (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, (sum.writeCacheEvictTime-lastReported.writeCacheEvictTime)/1000, sum.writeCacheEvictTime/1000, - sum.storageUpdateCount-lastReported.storageUpdateCount, (sum.storageUpdateTime-lastReported.storageUpdateTime)/1000, sum.storageUpdateTime/1000, - sum.storagePersistCount-lastReported.storagePersistCount, (sum.storagePersistTime-lastReported.storagePersistTime)/1000, sum.storagePersistTime/1000, + storageUpdateCount, storageUpdateTime.Milliseconds(), storageUpdateAvg, sum.storageUpdateTime.Milliseconds(), + storagePersistCount, storagePersistTime.Milliseconds(), storagePersistAvg, sum.storagePersistTime.Milliseconds(), blockNumber-oldest.updatedAt, ) quants := []float64{0.05, 0.1, 0.25, 0.5, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95} From 0c5f2b4982abe47367b4787b9c9ec532f3830e18 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 2 Jan 2025 21:11:43 -0800 Subject: [PATCH 194/307] ms --- plugin/evm/post_processing_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index e513710980..bb0f770b4a 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -42,7 +42,7 @@ type totals struct { writeCacheEvictStorage uint64 // eviction (historical state storage update) time - writeCacheEvictTime uint64 + writeCacheEvictTime time.Duration // update time (historical state state commitment + persistence) storageUpdateTime time.Duration @@ -226,7 +226,7 @@ func TestPostProcess(t *testing.T) { if storage != nil { evitcedBatch[k] = v.val } - sum.writeCacheEvictTime += uint64(time.Since(now).Nanoseconds()) + sum.writeCacheEvictTime += time.Since(now) } var writeCache writeCache[string, withUpdatedAt] = &noCache[string, withUpdatedAt]{ @@ -452,7 +452,7 @@ func TestPostProcess(t *testing.T) { float64(sum.writeCacheEvictAccount-lastReported.writeCacheEvictAccount)/float64(txs), float64(sum.writeCacheEvictStorage-lastReported.writeCacheEvictStorage)/float64(txs), (sum.writeCacheEvictAccount+sum.writeCacheEvictStorage)/1000, - (sum.writeCacheEvictTime-lastReported.writeCacheEvictTime)/1000, sum.writeCacheEvictTime/1000, + (sum.writeCacheEvictTime - lastReported.writeCacheEvictTime).Milliseconds(), sum.writeCacheEvictTime.Milliseconds(), storageUpdateCount, storageUpdateTime.Milliseconds(), storageUpdateAvg, sum.storageUpdateTime.Milliseconds(), storagePersistCount, storagePersistTime.Milliseconds(), storagePersistAvg, sum.storagePersistTime.Milliseconds(), blockNumber-oldest.updatedAt, From 95a2bfb5f38ff926a81fd0325123280de35f86d5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 08:34:02 -0800 Subject: [PATCH 195/307] update formatting for negative state growth --- plugin/evm/post_processing_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index bb0f770b4a..5e2374c6b3 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -30,8 +30,11 @@ type totals struct { storageUpdates uint64 accountDeletes uint64 storageDeletes uint64 - accounts uint64 - storage uint64 + + // These are int64 as we want to compute the difference (since last log), + // and state may be deleted. + accounts int64 + storage int64 // cache stats accountReadHits uint64 @@ -397,8 +400,8 @@ func TestPostProcess(t *testing.T) { sum.storageUpdates += uint64(storageUpdates) sum.accountDeletes += uint64(accountDeletes) sum.storageDeletes += uint64(storageDeletes) - sum.accounts += uint64(int(accountWrites) - accountUpdates - 2*accountDeletes) - sum.storage += uint64(int(storageWrites) - storageUpdates - 2*storageDeletes) + sum.accounts += int64(int(accountWrites) - accountUpdates - 2*accountDeletes) + sum.storage += int64(int(storageWrites) - storageUpdates - 2*storageDeletes) if blockNumber%uint64(logEach) == 0 { storageRootStr := "" From eb5df375ce767edfaf87de639379ee4587b0a4b5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:08:48 -0800 Subject: [PATCH 196/307] allow creartion of continuable db through pp --- plugin/evm/post_processing_test.go | 37 ++++++++++++++++++++++++++++++ plugin/evm/reprocess_test.go | 2 ++ 2 files changed, 39 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 5e2374c6b3..863b304330 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -9,9 +9,11 @@ import ( "github.com/VictoriaMetrics/fastcache" "github.com/Yiling-J/theine-go" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/shim/legacy" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" lru "github.com/hashicorp/golang-lru/v2" "github.com/maypok86/otter" "github.com/stretchr/testify/require" @@ -180,6 +182,7 @@ func TestPostProcess(t *testing.T) { var ( dbs dbs + sourceDb ethdb.Database sum totals blockNumber uint64 storageRoot common.Hash @@ -190,6 +193,11 @@ func TestPostProcess(t *testing.T) { number uint64 } ) + if sourceDbDir != "" { + sourceDb = openSourceDB(t) + defer sourceDb.Close() + } + if storageBackend != "none" { dbs = openDBs(t) defer dbs.Close() @@ -386,6 +394,35 @@ func TestPostProcess(t *testing.T) { sum.storagePersistCount++ sum.storageUpdateCount++ + if writeSnapshot { + for _, kv := range batch { + if len(kv.Key) == 32 { + rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), kv.Value) + } else { + rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) + } + } + } + if sourceDb != nil { + // update block and metadata from source db + hash := rawdb.ReadCanonicalHash(sourceDb, blockNumber) + require.Equal(t, blockHash, hash, "Block hash mismatch") + + block := rawdb.ReadBlock(sourceDb, hash, blockNumber) + require.NotNil(t, block, "Block not found in source db") + + b := dbs.chain.NewBatch() + rawdb.WriteCanonicalHash(b, hash, blockNumber) + rawdb.WriteBlock(b, block) + + // update metadata + rawdb.WriteAcceptorTip(b, blockHash) + rawdb.WriteHeadBlockHash(b, blockHash) + rawdb.WriteHeadHeaderHash(b, blockHash) + rawdb.WriteSnapshotBlockHash(b, blockHash) + rawdb.WriteSnapshotRoot(b, block.Root()) // TODO: unsure if this should be block.Root() or storageRoot + } + updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) lastCommit.number = blockNumber lastCommit.txs = sum.txs + sum.atomicTxs diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index d18e02ce2e..82770c9456 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -40,6 +40,7 @@ var ( endBlock = uint64(200) prefetchers = 4 useSnapshot = true + writeSnapshot = false pruning = true skipUpgradeCheck = false usePersistedStartBlock = false @@ -74,6 +75,7 @@ func TestMain(m *testing.M) { flag.Uint64Var(&endBlock, "endBlock", endBlock, "end block number") flag.IntVar(&prefetchers, "prefetchers", prefetchers, "number of prefetchers") flag.BoolVar(&useSnapshot, "useSnapshot", useSnapshot, "use snapshot") + flag.BoolVar(&writeSnapshot, "writeSnapshot", writeSnapshot, "write snapshot") flag.BoolVar(&pruning, "pruning", pruning, "pruning") flag.BoolVar(&skipUpgradeCheck, "skipUpgradeCheck", skipUpgradeCheck, "skip upgrade check") flag.BoolVar(&usePersistedStartBlock, "usePersistedStartBlock", usePersistedStartBlock, "use persisted start block") From 13564f4a92fc3a01b13a15b31566e4f11a59d7fb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:18:38 -0800 Subject: [PATCH 197/307] handle genesis + write batch --- plugin/evm/post_processing_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 863b304330..fe7c186ae2 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -421,6 +421,19 @@ func TestPostProcess(t *testing.T) { rawdb.WriteHeadHeaderHash(b, blockHash) rawdb.WriteSnapshotBlockHash(b, blockHash) rawdb.WriteSnapshotRoot(b, block.Root()) // TODO: unsure if this should be block.Root() or storageRoot + + // handle genesis + if blockNumber == 1 { + genesisHash := rawdb.ReadCanonicalHash(sourceDb, 0) + require.NotNil(t, genesisHash, "Genesis hash not found in source db") + rawdb.WriteCanonicalHash(b, genesisHash, 0) + + genesisChainConfig := rawdb.ReadChainConfig(sourceDb, genesisHash) + require.NotNil(t, genesisChainConfig, "Genesis chain config not found in source db") + rawdb.WriteChainConfig(b, genesisHash, genesisChainConfig) + } + + require.NoError(t, b.Write()) } updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) From 3769b9b2c053004f4d5208ea3710102575421d3b Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:21:14 -0800 Subject: [PATCH 198/307] try --- plugin/evm/post_processing_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index fe7c186ae2..29ddb98373 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -424,8 +424,9 @@ func TestPostProcess(t *testing.T) { // handle genesis if blockNumber == 1 { + t.Logf("Updating genesis") genesisHash := rawdb.ReadCanonicalHash(sourceDb, 0) - require.NotNil(t, genesisHash, "Genesis hash not found in source db") + require.NotZero(t, genesisHash, "Genesis hash not found in source db") rawdb.WriteCanonicalHash(b, genesisHash, 0) genesisChainConfig := rawdb.ReadChainConfig(sourceDb, genesisHash) From f2bf587c41262b4e3248c6d661323c2d0968d9e9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:22:47 -0800 Subject: [PATCH 199/307] try --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 29ddb98373..f82e209ef8 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -423,7 +423,7 @@ func TestPostProcess(t *testing.T) { rawdb.WriteSnapshotRoot(b, block.Root()) // TODO: unsure if this should be block.Root() or storageRoot // handle genesis - if blockNumber == 1 { + if lastCommit.number == 0 { t.Logf("Updating genesis") genesisHash := rawdb.ReadCanonicalHash(sourceDb, 0) require.NotZero(t, genesisHash, "Genesis hash not found in source db") From 0094036388a6b10c364d00c8d75b9db3a3875568 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:27:53 -0800 Subject: [PATCH 200/307] try --- plugin/evm/post_processing_test.go | 10 +++------- plugin/evm/reprocess_backend_test.go | 6 +++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f82e209ef8..ff01e0ce3f 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -424,14 +424,10 @@ func TestPostProcess(t *testing.T) { // handle genesis if lastCommit.number == 0 { - t.Logf("Updating genesis") - genesisHash := rawdb.ReadCanonicalHash(sourceDb, 0) - require.NotZero(t, genesisHash, "Genesis hash not found in source db") + genesis := getMainnetGenesis(t) + genesisHash := genesis.ToBlock().Hash() rawdb.WriteCanonicalHash(b, genesisHash, 0) - - genesisChainConfig := rawdb.ReadChainConfig(sourceDb, genesisHash) - require.NotNil(t, genesisChainConfig, "Genesis chain config not found in source db") - rawdb.WriteChainConfig(b, genesisHash, genesisChainConfig) + t.Logf("Updating genesis hash: %s", genesisHash.TerminalString()) } require.NoError(t, b.Write()) diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index 3dd0099b11..f0273cbcff 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -156,7 +156,7 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB } } -func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs) *reprocessBackend { +func getMainnetGenesis(t *testing.T) core.Genesis { var g core.Genesis require.NoError(t, json.Unmarshal([]byte(cChainGenesisMainnet), &g)) // Update the chain config with mainnet upgrades @@ -174,7 +174,11 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs } t.Logf("Mainnet chain config: %v", g.Config) + return g +} +func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs) *reprocessBackend { + g := getMainnetGenesis(t) testVM := &VM{ chainConfig: g.Config, ctx: g.Config.SnowCtx, From 0f1e1641a112aa447f35b48a30a1f965ed1e7602 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:30:49 -0800 Subject: [PATCH 201/307] try --- plugin/evm/post_processing_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index ff01e0ce3f..5242b7f435 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -427,6 +427,7 @@ func TestPostProcess(t *testing.T) { genesis := getMainnetGenesis(t) genesisHash := genesis.ToBlock().Hash() rawdb.WriteCanonicalHash(b, genesisHash, 0) + rawdb.WriteBlock(b, genesis.ToBlock()) t.Logf("Updating genesis hash: %s", genesisHash.TerminalString()) } From e23047861eb9aeaa5a90fbb6509fd892b167ccc3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 09:32:08 -0800 Subject: [PATCH 202/307] snapshot --- plugin/evm/post_processing_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 5242b7f435..5bc1d6ca86 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -10,6 +10,7 @@ import ( "github.com/Yiling-J/theine-go" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/coreth/core/rawdb" + "github.com/ava-labs/coreth/core/state/snapshot" "github.com/ava-labs/coreth/shim/legacy" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" @@ -428,6 +429,7 @@ func TestPostProcess(t *testing.T) { genesisHash := genesis.ToBlock().Hash() rawdb.WriteCanonicalHash(b, genesisHash, 0) rawdb.WriteBlock(b, genesis.ToBlock()) + snapshot.ResetSnapshotGeneration(b) t.Logf("Updating genesis hash: %s", genesisHash.TerminalString()) } From ec47722bdbec5f83aa600e4b15ff8247a820b6ee Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 10:09:21 -0800 Subject: [PATCH 203/307] add code export support --- plugin/evm/reprocess_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 82770c9456..740f930634 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -168,6 +168,33 @@ func TestExportBlocks(t *testing.T) { t.Logf("Exported %d blocks", endBlock-startBlock+1) } +func TestExportCode(t *testing.T) { + sourceDb := openSourceDB(t) + defer sourceDb.Close() + + db, err := rawdb.NewLevelDBDatabase(dbDir, cacheSize, handles, "", false) + require.NoError(t, err) + defer db.Close() + + it := sourceDb.NewIterator(rawdb.CodePrefix, nil) + defer it.Release() + + count, bytes := uint64(0), uint64(0) + for it.Next() { + if len(it.Key()) != 33 { + continue + } + hash := common.BytesToHash(it.Key()[1:]) + rawdb.WriteCode(db, hash, it.Value()) + count++ + bytes += uint64(len(it.Value())) + + if count%uint64(logEach) == 0 { + t.Logf("Exported %d code entries (%d MBs)", count, bytes/(1024*1024)) + } + } +} + func TestQueryBlock(t *testing.T) { sourceDb := openSourceDB(t) defer sourceDb.Close() From b6554942b8e14e29f1bc3398b7bbcfcabdaac779 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 10:18:44 -0800 Subject: [PATCH 204/307] detect root divergance --- plugin/evm/post_processing_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 5bc1d6ca86..5a5cd2894c 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -434,6 +434,10 @@ func TestPostProcess(t *testing.T) { } require.NoError(t, b.Write()) + + if storageBackend == "legacy" { + require.Equal(t, storageRoot, block.Root(), "Root mismatch") + } } updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) From 51b20ff381f6cae7e3e88efc822baa0dff2edc89 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 10:23:28 -0800 Subject: [PATCH 205/307] add log --- plugin/evm/post_processing_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 5a5cd2894c..01222f1963 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -436,6 +436,7 @@ func TestPostProcess(t *testing.T) { require.NoError(t, b.Write()) if storageBackend == "legacy" { + t.Logf("Verified root: %s", block.Root().TerminalString()) require.Equal(t, storageRoot, block.Root(), "Root mismatch") } } From f6ff6f1ff5217c12370731866ae78a318f34bbe5 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:28:05 -0800 Subject: [PATCH 206/307] add iteration --- plugin/evm/reprocess_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 740f930634..6192025cff 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -116,6 +116,10 @@ func (r *prefixReader) Has(key []byte) (bool, error) { return r.Database.Has(append(r.prefix, key...)) } +func (r *prefixReader) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + return r.Database.NewIterator(append(r.prefix, prefix...), start) +} + const ( cacheSize = 128 handles = 1024 From 849835625146f2ae8ac744d68ac1ef4c1ea23278 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:33:39 -0800 Subject: [PATCH 207/307] try --- plugin/evm/reprocess_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 6192025cff..aaf340721a 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -185,10 +185,13 @@ func TestExportCode(t *testing.T) { count, bytes := uint64(0), uint64(0) for it.Next() { - if len(it.Key()) != 33 { + // break of abstraction here, easier than wrapping the iterator + if len(it.Key()) != 33+len(sourcePrefix) { continue } - hash := common.BytesToHash(it.Key()[1:]) + acc := it.Key()[1+len(sourcePrefix):] + + hash := common.BytesToHash(acc) rawdb.WriteCode(db, hash, it.Value()) count++ bytes += uint64(len(it.Value())) From 73ea7e6ddcab3850c8199ce83a88a79f02c9ea38 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:36:17 -0800 Subject: [PATCH 208/307] try --- plugin/evm/reprocess_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index aaf340721a..bbb4585a1c 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -117,7 +117,16 @@ func (r *prefixReader) Has(key []byte) (bool, error) { } func (r *prefixReader) NewIterator(prefix []byte, start []byte) ethdb.Iterator { - return r.Database.NewIterator(append(r.prefix, prefix...), start) + return prefixIt{r.Database.NewIterator(append(r.prefix, prefix...), start), r.prefix} +} + +type prefixIt struct { + ethdb.Iterator + prefix []byte +} + +func (it prefixIt) Key() []byte { + return it.Iterator.Key()[len(it.prefix):] } const ( @@ -185,11 +194,10 @@ func TestExportCode(t *testing.T) { count, bytes := uint64(0), uint64(0) for it.Next() { - // break of abstraction here, easier than wrapping the iterator - if len(it.Key()) != 33+len(sourcePrefix) { + if len(it.Key()) != 33 { continue } - acc := it.Key()[1+len(sourcePrefix):] + acc := it.Key()[1:] hash := common.BytesToHash(acc) rawdb.WriteCode(db, hash, it.Value()) From 0ff0a5b8fee35ab40ba9608144c543c2c8bd4d93 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:44:08 -0800 Subject: [PATCH 209/307] try --- plugin/evm/reprocess_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index bbb4585a1c..ead2bf6c7e 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" ) var ( @@ -192,6 +193,7 @@ func TestExportCode(t *testing.T) { it := sourceDb.NewIterator(rawdb.CodePrefix, nil) defer it.Release() + h := sha3.NewLegacyKeccak256() count, bytes := uint64(0), uint64(0) for it.Next() { if len(it.Key()) != 33 { @@ -200,6 +202,12 @@ func TestExportCode(t *testing.T) { acc := it.Key()[1:] hash := common.BytesToHash(acc) + code := it.Value() + _, err = h.Write(code) + require.NoError(t, err) + require.Equal(t, hash, common.BytesToHash(h.Sum(nil))) + h.Reset() + rawdb.WriteCode(db, hash, it.Value()) count++ bytes += uint64(len(it.Value())) From a006119e88cb5ede827136405be350cde97570f7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:51:41 -0800 Subject: [PATCH 210/307] try --- plugin/evm/reprocess_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index ead2bf6c7e..c9f986c154 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -118,7 +118,10 @@ func (r *prefixReader) Has(key []byte) (bool, error) { } func (r *prefixReader) NewIterator(prefix []byte, start []byte) ethdb.Iterator { - return prefixIt{r.Database.NewIterator(append(r.prefix, prefix...), start), r.prefix} + pfx := make([]byte, len(r.prefix)+len(prefix)) + copy(pfx, r.prefix) + copy(pfx[len(r.prefix):], prefix) + return prefixIt{r.Database.NewIterator(pfx, start), r.prefix} } type prefixIt struct { From 47a4176f6a0e853a6d08a35e5617b6a4eab5437c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:52:48 -0800 Subject: [PATCH 211/307] try --- plugin/evm/reprocess_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c9f986c154..d57254f364 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -198,6 +198,7 @@ func TestExportCode(t *testing.T) { h := sha3.NewLegacyKeccak256() count, bytes := uint64(0), uint64(0) + target := common.HexToHash("774f38ab67f94b7d7d2a1bccebb8b39223e7026f5f495ea34d98980289079c4d") for it.Next() { if len(it.Key()) != 33 { continue @@ -211,6 +212,10 @@ func TestExportCode(t *testing.T) { require.Equal(t, hash, common.BytesToHash(h.Sum(nil))) h.Reset() + if hash == target { + t.Logf("Code: %x", code) + } + rawdb.WriteCode(db, hash, it.Value()) count++ bytes += uint64(len(it.Value())) From f90888d55059025121490f7c30cb56f6c4a16ddd Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:54:48 -0800 Subject: [PATCH 212/307] try --- plugin/evm/reprocess_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index d57254f364..7228df16ae 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -189,9 +189,10 @@ func TestExportCode(t *testing.T) { sourceDb := openSourceDB(t) defer sourceDb.Close() - db, err := rawdb.NewLevelDBDatabase(dbDir, cacheSize, handles, "", false) - require.NoError(t, err) - defer db.Close() + dbs := openDBs(t) + defer dbs.Close() + + db := dbs.chain it := sourceDb.NewIterator(rawdb.CodePrefix, nil) defer it.Release() From 946afa9925d29082b5b77d9bace27532e9f36b8e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 3 Jan 2025 11:55:17 -0800 Subject: [PATCH 213/307] try --- plugin/evm/reprocess_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 7228df16ae..23c9c5c206 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -199,7 +199,6 @@ func TestExportCode(t *testing.T) { h := sha3.NewLegacyKeccak256() count, bytes := uint64(0), uint64(0) - target := common.HexToHash("774f38ab67f94b7d7d2a1bccebb8b39223e7026f5f495ea34d98980289079c4d") for it.Next() { if len(it.Key()) != 33 { continue @@ -208,15 +207,11 @@ func TestExportCode(t *testing.T) { hash := common.BytesToHash(acc) code := it.Value() - _, err = h.Write(code) + _, err := h.Write(code) require.NoError(t, err) require.Equal(t, hash, common.BytesToHash(h.Sum(nil))) h.Reset() - if hash == target { - t.Logf("Code: %x", code) - } - rawdb.WriteCode(db, hash, it.Value()) count++ bytes += uint64(len(it.Value())) From c1fcee6cdec4787bea9b671ebbbf644875f08bea Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 04:54:02 -0800 Subject: [PATCH 214/307] oops --- shim/legacy/legacy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 26724b2223..df069a9d1d 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -57,6 +57,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if !ok { tr, err = trie.New(trie.StorageTrieID(l.root, accHash, root), l.triedb) if err != nil { + return common.Hash{}, err } tries[accHash] = tr } From e158f0327a91a8af9b3aa7e9b7c01c3ef86a4e28 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 05:49:20 -0800 Subject: [PATCH 215/307] verify expectation --- shim/legacy/legacy.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index df069a9d1d..9e0e21913f 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -1,6 +1,8 @@ package legacy import ( + "fmt" + "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -85,6 +87,22 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } accounts.MustUpdate(kv.Key, kv.Value) } + + // Verify account trie updates match the storage trie updates + for accHash, tr := range tries { + accBytes, err := accounts.Get(accHash[:]) + if err != nil { + return common.Hash{}, err + } + var acc types.StateAccount + if err := rlp.DecodeBytes(accBytes, &acc); err != nil { + return common.Hash{}, err + } + if acc.Root != tr.Hash() { + return common.Hash{}, fmt.Errorf("account trie root mismatch: %x != %x", acc.Root, tr.Hash()) + } + } + next, set, err := accounts.Commit(true) if err != nil { return common.Hash{}, err From aa78bdbd66a37961a1a5e40c6ea9f6fd94ce394d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 05:54:52 -0800 Subject: [PATCH 216/307] check --- shim/legacy/legacy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 9e0e21913f..3513bad3e9 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -51,7 +51,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if accBytes != nil { var acc types.StateAccount if err := rlp.DecodeBytes(accBytes, &acc); err != nil { - return common.Hash{}, err + return common.Hash{}, fmt.Errorf("failed to decode account: %w", err) } root = acc.Root } @@ -96,7 +96,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } var acc types.StateAccount if err := rlp.DecodeBytes(accBytes, &acc); err != nil { - return common.Hash{}, err + return common.Hash{}, fmt.Errorf("failed to decode account (%x): %w", accBytes, err) } if acc.Root != tr.Hash() { return common.Hash{}, fmt.Errorf("account trie root mismatch: %x != %x", acc.Root, tr.Hash()) From 470c707ffc88fd78db2121189bb7a63b16829f70 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 05:57:42 -0800 Subject: [PATCH 217/307] try --- shim/legacy/legacy.go | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 3513bad3e9..de339bea82 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -29,6 +29,22 @@ func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bo } } +func getAccountRoot(tr *trie.Trie, accHash common.Hash) (common.Hash, error) { + root := types.EmptyRootHash + accBytes, err := tr.Get(accHash[:]) + if err != nil { + return common.Hash{}, err + } + if len(accBytes) > 0 { + var acc types.StateAccount + if err := rlp.DecodeBytes(accBytes, &acc); err != nil { + return common.Hash{}, fmt.Errorf("failed to decode account: %w", err) + } + root = acc.Root + } + return root, nil +} + func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) if err != nil { @@ -43,18 +59,11 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { continue } accHash := common.BytesToHash(kv.Key[:32]) - accBytes, err := accounts.Get(kv.Key[:32]) + root, err := getAccountRoot(accounts, accHash) if err != nil { return common.Hash{}, err } - root := types.EmptyRootHash - if accBytes != nil { - var acc types.StateAccount - if err := rlp.DecodeBytes(accBytes, &acc); err != nil { - return common.Hash{}, fmt.Errorf("failed to decode account: %w", err) - } - root = acc.Root - } + tr, ok := tries[accHash] if !ok { tr, err = trie.New(trie.StorageTrieID(l.root, accHash, root), l.triedb) @@ -90,16 +99,12 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { // Verify account trie updates match the storage trie updates for accHash, tr := range tries { - accBytes, err := accounts.Get(accHash[:]) + root, err := getAccountRoot(accounts, accHash) if err != nil { return common.Hash{}, err } - var acc types.StateAccount - if err := rlp.DecodeBytes(accBytes, &acc); err != nil { - return common.Hash{}, fmt.Errorf("failed to decode account (%x): %w", accBytes, err) - } - if acc.Root != tr.Hash() { - return common.Hash{}, fmt.Errorf("account trie root mismatch: %x != %x", acc.Root, tr.Hash()) + if root != tr.Hash() { + return common.Hash{}, fmt.Errorf("account trie root mismatch (%x != %x)", root, tr.Hash()) } } From e2b6679c481903d928dbb60820f9d2724b98ab6f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 06:01:35 -0800 Subject: [PATCH 218/307] try --- shim/legacy/legacy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index de339bea82..297a71a2b6 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -104,7 +104,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { return common.Hash{}, err } if root != tr.Hash() { - return common.Hash{}, fmt.Errorf("account trie root mismatch (%x != %x)", root, tr.Hash()) + return common.Hash{}, fmt.Errorf("account %x trie root mismatch (%x != %x)", accHash, root, tr.Hash()) } } From fe552dd1158951180ccaed0584147cf0f393da96 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 06:06:57 -0800 Subject: [PATCH 219/307] try --- shim/legacy/legacy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 297a71a2b6..6cb3e9d252 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -68,7 +68,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if !ok { tr, err = trie.New(trie.StorageTrieID(l.root, accHash, root), l.triedb) if err != nil { - return common.Hash{}, err + return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) } tries[accHash] = tr } From fb80d8ac03a06d0e5f42d0ae6eecf003dcebb302 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 06:08:38 -0800 Subject: [PATCH 220/307] remove log --- plugin/evm/post_processing_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 01222f1963..5a5cd2894c 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -436,7 +436,6 @@ func TestPostProcess(t *testing.T) { require.NoError(t, b.Write()) if storageBackend == "legacy" { - t.Logf("Verified root: %s", block.Root().TerminalString()) require.Equal(t, storageRoot, block.Root(), "Root mismatch") } } From 46e0d05b81abd0944eeb0d34fb49dcc7b6098173 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 07:03:11 -0800 Subject: [PATCH 221/307] try handle deletes --- shim/legacy/legacy.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 6cb3e9d252..8ec494c8b3 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -55,10 +55,21 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { // the hash scheme. tries := make(map[common.Hash]*trie.Trie) for _, kv := range batch { - if len(kv.Key) != 64 { + accHash := common.BytesToHash(kv.Key[:32]) + if len(kv.Key) == 32 { + if len(kv.Value) == 0 { + // this trie is DELETED, so if it was updated before these updates should not be applied + // further updates shold apply to an empty trie + tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) + } + } + + // otherwise, skip account updates for now continue } - accHash := common.BytesToHash(kv.Key[:32]) + root, err := getAccountRoot(accounts, accHash) if err != nil { return common.Hash{}, err From 078a3553ed884649475a1da22c472ac89d97c3ed Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 07:10:22 -0800 Subject: [PATCH 222/307] preserve order --- plugin/evm/post_processing_test.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 5a5cd2894c..dfd7e2897e 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -188,7 +188,7 @@ func TestPostProcess(t *testing.T) { blockNumber uint64 storageRoot common.Hash storage triedb.KVBackend - evitcedBatch = make(map[string][]byte) + evitcedBatch triedb.Batch lastCommit struct { txs uint64 number uint64 @@ -236,7 +236,7 @@ func TestPostProcess(t *testing.T) { } // t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) if storage != nil { - evitcedBatch[k] = v.val + evitcedBatch = append(evitcedBatch, triedb.KV{Key: []byte(k), Value: v.val}) } sum.writeCacheEvictTime += time.Since(now) } @@ -371,17 +371,13 @@ func TestPostProcess(t *testing.T) { shouldCommitTxs := commitEachTxs > 0 && sum.txs+sum.atomicTxs-lastCommit.txs >= uint64(commitEachTxs) if len(evitcedBatch) > 0 && (shouldCommitBlocks || shouldCommitTxs) { if tapeVerbose { - for k, v := range evitcedBatch { - t.Logf("storing: %x -> %x", k, v) + for _, kv := range evitcedBatch { + t.Logf("storing: %x -> %x", kv.Key, kv.Value) } } - batch := make(triedb.Batch, 0, len(evitcedBatch)) - for k, v := range evitcedBatch { - batch = append(batch, triedb.KV{Key: []byte(k), Value: v}) - } now := time.Now() // Get state commitment from storage backend - storageRoot, err = storage.Update(batch) + storageRoot, err = storage.Update(evitcedBatch) require.NoError(t, err) clear(evitcedBatch) updateTime := time.Since(now) From c8e9a7b5b2122f057720c07637af849d865d14ca Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 07:11:47 -0800 Subject: [PATCH 223/307] try --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index dfd7e2897e..6a8f59ae5d 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -392,7 +392,7 @@ func TestPostProcess(t *testing.T) { sum.storageUpdateCount++ if writeSnapshot { - for _, kv := range batch { + for _, kv := range evitcedBatch { if len(kv.Key) == 32 { rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), kv.Value) } else { From 013c23a0ee5486936f4d6efad9bac5e59cdf27c3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 07:13:35 -0800 Subject: [PATCH 224/307] try --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 6a8f59ae5d..b58c2f6716 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -379,7 +379,7 @@ func TestPostProcess(t *testing.T) { // Get state commitment from storage backend storageRoot, err = storage.Update(evitcedBatch) require.NoError(t, err) - clear(evitcedBatch) + evitcedBatch = evitcedBatch[:0] updateTime := time.Since(now) // Request storage backend to persist the state From ad035d3ed10383be6687a61c50f6913a88aa19c3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 07:27:25 -0800 Subject: [PATCH 225/307] try --- plugin/evm/post_processing_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index b58c2f6716..e1ed4049d5 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -395,6 +395,16 @@ func TestPostProcess(t *testing.T) { for _, kv := range evitcedBatch { if len(kv.Key) == 32 { rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), kv.Value) + if len(kv.Value) == 0 { + it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) + for it.Next() { + k := it.Key()[len(rawdb.SnapshotStoragePrefix):] + rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) + } + if err := it.Error(); err != nil { + t.Fatalf("Failed to iterate over snapshot account: %v", err) + } + it.Release() } else { rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) } From 4338bdc5ed6240269f79d156469d8fa98a254788 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 07:28:15 -0800 Subject: [PATCH 226/307] try --- plugin/evm/post_processing_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index e1ed4049d5..bca873771c 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -405,6 +405,7 @@ func TestPostProcess(t *testing.T) { t.Fatalf("Failed to iterate over snapshot account: %v", err) } it.Release() + } } else { rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) } From 9a8d485928da1b8fba8f8ea1ac919f21ce48e684 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 08:12:43 -0800 Subject: [PATCH 227/307] fix --- plugin/evm/post_processing_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index bca873771c..e37be71df5 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -379,7 +379,6 @@ func TestPostProcess(t *testing.T) { // Get state commitment from storage backend storageRoot, err = storage.Update(evitcedBatch) require.NoError(t, err) - evitcedBatch = evitcedBatch[:0] updateTime := time.Since(now) // Request storage backend to persist the state @@ -410,6 +409,8 @@ func TestPostProcess(t *testing.T) { rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) } } + + evitcedBatch = evitcedBatch[:0] } if sourceDb != nil { // update block and metadata from source db From ad58a06372700659341336b0f7acd0b1f4d48f4c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 10:11:10 -0800 Subject: [PATCH 228/307] log ethdb prefix --- plugin/evm/reprocess_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 23c9c5c206..204ce2bc69 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -313,6 +313,8 @@ func TestCalculatePrefix(t *testing.T) { prefix = append(prefix, prefixdb.MakePrefix(ethDBPrefix)...) t.Logf("Prefix: %x", prefix) + + t.Logf("Prefix: %x", prefixdb.MakePrefix(ethDBPrefix)) } func init() { From ebc817ec9757f4bb8e916ae107615cdee89f10ca Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 11:08:30 -0800 Subject: [PATCH 229/307] try --- plugin/evm/post_processing_test.go | 11 +++++++++++ shim/legacy/legacy.go | 9 ++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index e37be71df5..240a303325 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -3,6 +3,7 @@ package evm import ( "fmt" "io" + "sync" "testing" "time" @@ -193,6 +194,7 @@ func TestPostProcess(t *testing.T) { txs uint64 number uint64 } + commitLock sync.Mutex ) if sourceDbDir != "" { sourceDb = openSourceDB(t) @@ -202,6 +204,11 @@ func TestPostProcess(t *testing.T) { if storageBackend != "none" { dbs = openDBs(t) defer dbs.Close() + CleanupOnInterrupt(func() { + commitLock.Lock() + dbs.Close() + commitLock.Unlock() + }) lastHash, lastRoot, lastHeight := getMetadata(dbs.metadata) t.Logf("Persisted metadata: Last hash: %x, Last root: %x, Last height: %d", lastHash, lastRoot, lastHeight) @@ -367,6 +374,8 @@ func TestPostProcess(t *testing.T) { sum.atomicTxs += uint64(atomicTxs) if storage != nil { + commitLock.Lock() + shouldCommitBlocks := commitEachBlocks > 0 && blockNumber-lastCommit.number >= uint64(commitEachBlocks) shouldCommitTxs := commitEachTxs > 0 && sum.txs+sum.atomicTxs-lastCommit.txs >= uint64(commitEachTxs) if len(evitcedBatch) > 0 && (shouldCommitBlocks || shouldCommitTxs) { @@ -446,6 +455,8 @@ func TestPostProcess(t *testing.T) { if storageBackend == "legacy" { require.Equal(t, storageRoot, block.Root(), "Root mismatch") } + + commitLock.Unlock() } updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 8ec494c8b3..0fb3813fd1 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -70,13 +70,12 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { continue } - root, err := getAccountRoot(accounts, accHash) - if err != nil { - return common.Hash{}, err - } - tr, ok := tries[accHash] if !ok { + root, err := getAccountRoot(accounts, accHash) + if err != nil { + return common.Hash{}, err + } tr, err = trie.New(trie.StorageTrieID(l.root, accHash, root), l.triedb) if err != nil { return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) From ea59141691049769007353d3d1e2dda5bbfb9037 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 11:10:29 -0800 Subject: [PATCH 230/307] fix --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 240a303325..451365c2a7 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -456,13 +456,13 @@ func TestPostProcess(t *testing.T) { require.Equal(t, storageRoot, block.Root(), "Root mismatch") } - commitLock.Unlock() } updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) lastCommit.number = blockNumber lastCommit.txs = sum.txs + sum.atomicTxs } + commitLock.Unlock() } sum.accountReads += uint64(len(tapeResult.accountReads)) From e4cae09ce596aeef94c743881063a7685a7040a1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 13:16:12 -0800 Subject: [PATCH 231/307] try --- core/state/statedb.go | 8 ++++++++ plugin/evm/post_processing_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index d6b5c9ab36..897c8c8364 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -46,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -630,6 +631,12 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } // Encode the account and update the account trie addr := obj.Address() + bytes, err := rlp.EncodeToBytes(obj.data) + if err != nil { + panic(fmt.Sprintf("failed to encode account: %v", err)) + } + fmt.Printf("updateStateObject %x -> %x\n", addr, bytes) + if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) } @@ -662,6 +669,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { } // Delete the account from the trie addr := obj.Address() + fmt.Printf("deleteStateObject %x\n", addr) if err := s.trie.DeleteAccount(addr); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) } diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 451365c2a7..587c7a39ee 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -367,6 +367,36 @@ func TestPostProcess(t *testing.T) { if tapeVerbose { t.Logf("storage write: %x -> %x", k, v) } + + // 3. Now we can pr + for _, kv := range accountWritesTape { + k, v := kv.Key, kv.Value + if prev, ok := tapeResult.accountReads[string(k)]; ok { + if len(prev) > 0 && len(v) == 0 { + accountDeletes++ + } else if len(prev) > 0 || (len(prev) == 0 && len(v) == 0) { + accountUpdates++ + } + } else if tapeVerbose { + t.Logf("account write without read: %x -> %x", k, v) + } + got, found := writeCache.Get(string(k)) + if found { + hst.Update(float64(blockNumber - got.updatedAt)) + hstWithReset.Update(float64(blockNumber - got.updatedAt)) + } else { + hst.Update(inf) + hstWithReset.Update(inf) + } + writeCache.Add(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) + if found { + sum.accountWriteHits++ + } + + if tapeVerbose { + t.Logf("account write: %x -> %x", k, v) + } + } } sum.blocks++ From d63d2b930b5a0d50ddfc7a6bf9e353b2e0836e74 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 13:18:22 -0800 Subject: [PATCH 232/307] ```oops --- plugin/evm/post_processing_test.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 587c7a39ee..451365c2a7 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -367,36 +367,6 @@ func TestPostProcess(t *testing.T) { if tapeVerbose { t.Logf("storage write: %x -> %x", k, v) } - - // 3. Now we can pr - for _, kv := range accountWritesTape { - k, v := kv.Key, kv.Value - if prev, ok := tapeResult.accountReads[string(k)]; ok { - if len(prev) > 0 && len(v) == 0 { - accountDeletes++ - } else if len(prev) > 0 || (len(prev) == 0 && len(v) == 0) { - accountUpdates++ - } - } else if tapeVerbose { - t.Logf("account write without read: %x -> %x", k, v) - } - got, found := writeCache.Get(string(k)) - if found { - hst.Update(float64(blockNumber - got.updatedAt)) - hstWithReset.Update(float64(blockNumber - got.updatedAt)) - } else { - hst.Update(inf) - hstWithReset.Update(inf) - } - writeCache.Add(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) - if found { - sum.accountWriteHits++ - } - - if tapeVerbose { - t.Logf("account write: %x -> %x", k, v) - } - } } sum.blocks++ From 7b0efa8d8a25b08a3d53c10586f82e34d7609b42 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 13:19:06 -0800 Subject: [PATCH 233/307] try --- core/state/statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 897c8c8364..a78709ec04 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -631,7 +631,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } // Encode the account and update the account trie addr := obj.Address() - bytes, err := rlp.EncodeToBytes(obj.data) + bytes, err := rlp.EncodeToBytes(&obj.data) if err != nil { panic(fmt.Sprintf("failed to encode account: %v", err)) } From 975b7475699a500c1957532bf4bdcb0086fa9249 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 13:21:49 -0800 Subject: [PATCH 234/307] try --- core/state/statedb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index a78709ec04..62d57017c8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -635,7 +635,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) { if err != nil { panic(fmt.Sprintf("failed to encode account: %v", err)) } - fmt.Printf("updateStateObject %x -> %x\n", addr, bytes) + fmt.Printf("updateStateObject %x -> %x\n", obj.addrHash, bytes) if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) @@ -669,7 +669,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { } // Delete the account from the trie addr := obj.Address() - fmt.Printf("deleteStateObject %x\n", addr) + fmt.Printf("deleteStateObject %x\n", obj.addrHash) if err := s.trie.DeleteAccount(addr); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) } From 2a60161d0e1dc496153680a988ef17caec1a00e7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 13:46:27 -0800 Subject: [PATCH 235/307] try --- core/state/state_object.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/state/state_object.go b/core/state/state_object.go index 8f0493ed3d..c70ebb3b8b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -279,6 +279,8 @@ func (s *stateObject) finalise(prefetch bool) { } } +var target = common.HexToHash("8745943f66c4324de3dcba5e992c0a19ff9f273c37c5279436d549a2ae51f0f6") + // updateTrie is responsible for persisting cached storage changes into the // object's storage trie. In case the storage trie is not yet loaded, this // function will load the trie automatically. If any issues arise during the @@ -302,6 +304,9 @@ func (s *stateObject) updateTrie() (Trie, error) { storage map[common.Hash][]byte origin map[common.Hash][]byte ) + if target == s.addrHash { + fmt.Printf("target (data.Root=%x, origin.Root=%x)\n", s.data.Root, s.origin.Root) + } tr, err := s.getTrie() if err != nil { s.db.setError(err) @@ -319,6 +324,9 @@ func (s *stateObject) updateTrie() (Trie, error) { var encoded []byte // rlp-encoded value to be used by the snapshot if (value == common.Hash{}) { + if target == s.addrHash { + fmt.Printf("delete %x\n", key) + } if err := tr.DeleteStorage(s.address, key[:]); err != nil { s.db.setError(err) return nil, err @@ -328,6 +336,9 @@ func (s *stateObject) updateTrie() (Trie, error) { // Encoding []byte cannot fail, ok to ignore the error. trimmed := common.TrimLeftZeroes(value[:]) encoded, _ = rlp.EncodeToBytes(trimmed) + if target == s.addrHash { + fmt.Printf("update %x %x\n", key, trimmed) + } if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil { s.db.setError(err) return nil, err From 281139c724205d2ac5b80838f928ce1195631643 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 14:37:21 -0800 Subject: [PATCH 236/307] try storage befor accounts --- plugin/evm/post_processing_test.go | 47 ++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 451365c2a7..116f5dac0b 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -20,6 +20,7 @@ import ( "github.com/maypok86/otter" "github.com/stretchr/testify/require" "github.com/valyala/histogram" + "golang.org/x/crypto/sha3" ) type totals struct { @@ -310,17 +311,26 @@ func TestPostProcess(t *testing.T) { accountUpdates, storageUpdates := 0, 0 accountDeletes, storageDeletes := 0, 0 + // 1. Read account writes from the tape as they come first + accountWritesBatch := make([]triedb.KV, accountWrites) for j := 0; j < int(accountWrites); j++ { k, v, err := readKV(r, 32) require.NoError(t, err) - if prev, ok := tapeResult.accountReads[string(k)]; ok { + accountWritesBatch[j] = triedb.KV{Key: k, Value: v} + } + + // 2. Process storage writes + for j := 0; j < int(storageWrites); j++ { + k, v, err := readKV(r, 64) + require.NoError(t, err) + if prev, ok := tapeResult.storageReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { - accountDeletes++ + storageDeletes++ } else if len(prev) > 0 || (len(prev) == 0 && len(v) == 0) { - accountUpdates++ + storageUpdates++ } } else if tapeVerbose { - t.Logf("account write without read: %x -> %x", k, v) + t.Logf("storage write without read: %x -> %x", k, v) } got, found := writeCache.Get(string(k)) if found { @@ -332,24 +342,25 @@ func TestPostProcess(t *testing.T) { } writeCache.Add(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { - sum.accountWriteHits++ + sum.storageWriteHits++ } if tapeVerbose { - t.Logf("account write: %x -> %x", k, v) + t.Logf("storage write: %x -> %x", k, v) } } - for j := 0; j < int(storageWrites); j++ { - k, v, err := readKV(r, 64) - require.NoError(t, err) - if prev, ok := tapeResult.storageReads[string(k)]; ok { + + // 3. Process account writes + for j := 0; j < int(accountWrites); j++ { + k, v := accountWritesBatch[j].Key, accountWritesBatch[j].Value + if prev, ok := tapeResult.accountReads[string(k)]; ok { if len(prev) > 0 && len(v) == 0 { - storageDeletes++ + accountDeletes++ } else if len(prev) > 0 || (len(prev) == 0 && len(v) == 0) { - storageUpdates++ + accountUpdates++ } } else if tapeVerbose { - t.Logf("storage write without read: %x -> %x", k, v) + t.Logf("account write without read: %x -> %x", k, v) } got, found := writeCache.Get(string(k)) if found { @@ -361,11 +372,11 @@ func TestPostProcess(t *testing.T) { } writeCache.Add(string(k), withUpdatedAt{val: v, updatedAt: blockNumber}) if found { - sum.storageWriteHits++ + sum.accountWriteHits++ } if tapeVerbose { - t.Logf("storage write: %x -> %x", k, v) + t.Logf("account write: %x -> %x", k, v) } } @@ -605,3 +616,9 @@ func processTape(t *testing.T, r io.Reader, tapeResult *tapeResult, cache func(k } return txCount } + +func TestXxx(t *testing.T) { + h := sha3.NewLegacyKeccak256() + h.Write(common.Hex2Bytes("ac7bbff258e5ff67efcbac43331033010676e22cbf7a9515a31e4e2b661b9b96")) + fmt.Printf("%x\n", h.Sum(nil)) +} From cbe40c210d06f882ed23159fbd18ffae8ced5362 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 14:39:16 -0800 Subject: [PATCH 237/307] undo logs --- core/state/state_object.go | 8 -------- core/state/statedb.go | 8 -------- 2 files changed, 16 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index c70ebb3b8b..8fc7cd17a4 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -279,8 +279,6 @@ func (s *stateObject) finalise(prefetch bool) { } } -var target = common.HexToHash("8745943f66c4324de3dcba5e992c0a19ff9f273c37c5279436d549a2ae51f0f6") - // updateTrie is responsible for persisting cached storage changes into the // object's storage trie. In case the storage trie is not yet loaded, this // function will load the trie automatically. If any issues arise during the @@ -304,9 +302,6 @@ func (s *stateObject) updateTrie() (Trie, error) { storage map[common.Hash][]byte origin map[common.Hash][]byte ) - if target == s.addrHash { - fmt.Printf("target (data.Root=%x, origin.Root=%x)\n", s.data.Root, s.origin.Root) - } tr, err := s.getTrie() if err != nil { s.db.setError(err) @@ -324,9 +319,6 @@ func (s *stateObject) updateTrie() (Trie, error) { var encoded []byte // rlp-encoded value to be used by the snapshot if (value == common.Hash{}) { - if target == s.addrHash { - fmt.Printf("delete %x\n", key) - } if err := tr.DeleteStorage(s.address, key[:]); err != nil { s.db.setError(err) return nil, err diff --git a/core/state/statedb.go b/core/state/statedb.go index 62d57017c8..d6b5c9ab36 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -46,7 +46,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -631,12 +630,6 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } // Encode the account and update the account trie addr := obj.Address() - bytes, err := rlp.EncodeToBytes(&obj.data) - if err != nil { - panic(fmt.Sprintf("failed to encode account: %v", err)) - } - fmt.Printf("updateStateObject %x -> %x\n", obj.addrHash, bytes) - if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) } @@ -669,7 +662,6 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { } // Delete the account from the trie addr := obj.Address() - fmt.Printf("deleteStateObject %x\n", obj.addrHash) if err := s.trie.DeleteAccount(addr); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) } From 6f0e94266f4109b243006f96915b951336cb34e0 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 4 Jan 2025 14:40:56 -0800 Subject: [PATCH 238/307] remove logs --- core/state/state_object.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 8fc7cd17a4..8f0493ed3d 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -328,9 +328,6 @@ func (s *stateObject) updateTrie() (Trie, error) { // Encoding []byte cannot fail, ok to ignore the error. trimmed := common.TrimLeftZeroes(value[:]) encoded, _ = rlp.EncodeToBytes(trimmed) - if target == s.addrHash { - fmt.Printf("update %x %x\n", key, trimmed) - } if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil { s.db.setError(err) return nil, err From c0d0b14e3ca5b76f8a75c439ec5b3135c11cf9d2 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 7 Jan 2025 10:34:11 -0800 Subject: [PATCH 239/307] add option to continue from hash db --- plugin/evm/reprocess_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 204ce2bc69..fcb2ee9353 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -56,6 +56,7 @@ var ( storageBackend = "none" commitEachBlocks = 1 commitEachTxs = 0 + forceStartWithMismatch = false // merkledb options merkleDBBranchFactor = 16 @@ -92,6 +93,7 @@ func TestMain(m *testing.M) { flag.StringVar(&storageBackend, "storageBackend", storageBackend, "storage backend (none, legacy, merkledb, nomt)") flag.IntVar(&commitEachBlocks, "commitEachBlocks", commitEachBlocks, "commit each N blocks") flag.IntVar(&commitEachTxs, "commitEachTxs", commitEachTxs, "commit each N transactions") + flag.BoolVar(&forceStartWithMismatch, "forceStartWithMismatch", forceStartWithMismatch, "force start with mismatch") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -371,6 +373,13 @@ func TestReprocessMainnetBlocks(t *testing.T) { if usePersistedStartBlock { startBlock = lastHeight } + if forceStartWithMismatch { + // recover the last hash / root from the source database. + // makes it possible to continue from a hash source database. + lastHash = rawdb.ReadCanonicalHash(source, startBlock) + block := rawdb.ReadBlock(source, lastHash, startBlock) + lastRoot = block.Root() + } require.Equal(t, lastHeight, startBlock, "Last height does not match start block") if lastHash != (common.Hash{}) { // Other than when genesis is not performed, start processing from the next block From b579c23864d73e64ba9946e395d470694a45b5ae Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 7 Jan 2025 10:39:59 -0800 Subject: [PATCH 240/307] prefix option --- plugin/evm/reprocess_test.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index fcb2ee9353..641d4f9dca 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -37,6 +37,7 @@ var ( sourceDbDir = "sourceDb" sourcePrefix = "" dbDir = "" + dbPrefix = "" startBlock = uint64(0) endBlock = uint64(200) prefetchers = 4 @@ -73,6 +74,7 @@ func TestMain(m *testing.M) { flag.StringVar(&sourceDbDir, "sourceDbDir", sourceDbDir, "directory of source database") flag.StringVar(&sourcePrefix, "sourcePrefix", sourcePrefix, "prefix of source database") flag.StringVar(&dbDir, "dbDir", dbDir, "directory to store database (uses memory if empty)") + flag.StringVar(&dbPrefix, "dbPrefix", dbPrefix, "prefix of database") flag.Uint64Var(&startBlock, "startBlock", startBlock, "start block number") flag.Uint64Var(&endBlock, "endBlock", endBlock, "end block number") flag.IntVar(&prefetchers, "prefetchers", prefetchers, "number of prefetchers") @@ -268,9 +270,23 @@ func openDBs(t *testing.T) dbs { base = db } + prefix := []byte(dbPrefix) + if bytes.HasPrefix(prefix, []byte("0x")) { + prefix = prefix[2:] + var err error + prefix, err = hex.DecodeString(string(prefix)) + if err != nil { + t.Fatalf("invalid hex prefix: %s", prefix) + } + } + + dbPrefix := ethDBPrefix + if len(prefix) > 0 { + dbPrefix = prefix + } return dbs{ metadata: prefixdb.New(reprocessMetadataPrefix, base), - chain: rawdb.NewDatabase(Database{prefixdb.New(ethDBPrefix, base)}), + chain: rawdb.NewDatabase(Database{prefixdb.New(dbPrefix, base)}), merkledb: prefixdb.New(merkledbPrefix, base), base: base, } From e248b9464801a3118a45a0a29ed862ece050d6c4 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 7 Jan 2025 10:46:50 -0800 Subject: [PATCH 241/307] try --- plugin/evm/reprocess_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 641d4f9dca..3f1fba05de 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -280,13 +280,18 @@ func openDBs(t *testing.T) dbs { } } - dbPrefix := ethDBPrefix + var chaindb ethdb.Database if len(prefix) > 0 { - dbPrefix = prefix + chaindb = &prefixReader{ + Database: rawdb.NewDatabase(Database{base}), + prefix: prefix, + } + } else { + chaindb = rawdb.NewDatabase(Database{prefixdb.New(ethDBPrefix, base)}) } return dbs{ metadata: prefixdb.New(reprocessMetadataPrefix, base), - chain: rawdb.NewDatabase(Database{prefixdb.New(dbPrefix, base)}), + chain: chaindb, merkledb: prefixdb.New(merkledbPrefix, base), base: base, } From 7618b6c23652d02c1e7f787b57022763ef95e9ee Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 7 Jan 2025 10:49:59 -0800 Subject: [PATCH 242/307] try --- plugin/evm/reprocess_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 3f1fba05de..c7439f23b4 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -397,9 +397,10 @@ func TestReprocessMainnetBlocks(t *testing.T) { if forceStartWithMismatch { // recover the last hash / root from the source database. // makes it possible to continue from a hash source database. - lastHash = rawdb.ReadCanonicalHash(source, startBlock) - block := rawdb.ReadBlock(source, lastHash, startBlock) + lastHash = rawdb.ReadCanonicalHash(dbs.chain, startBlock) + block := rawdb.ReadBlock(dbs.chain, lastHash, startBlock) lastRoot = block.Root() + t.Logf("Forcing start with mismatch: Last hash: %x, Last root: %x", lastHash, lastRoot) } require.Equal(t, lastHeight, startBlock, "Last height does not match start block") if lastHash != (common.Hash{}) { From 5b60dd662aeef150b03ba008ca7ec047fe54dcf7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 7 Jan 2025 10:51:52 -0800 Subject: [PATCH 243/307] fix --- plugin/evm/reprocess_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c7439f23b4..de46c712cc 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -399,7 +399,7 @@ func TestReprocessMainnetBlocks(t *testing.T) { // makes it possible to continue from a hash source database. lastHash = rawdb.ReadCanonicalHash(dbs.chain, startBlock) block := rawdb.ReadBlock(dbs.chain, lastHash, startBlock) - lastRoot = block.Root() + lastHeight, lastRoot = startBlock, block.Root() t.Logf("Forcing start with mismatch: Last hash: %x, Last root: %x", lastHash, lastRoot) } require.Equal(t, lastHeight, startBlock, "Last height does not match start block") From d6664bee80ac989c5b66d18d1b61ecbec68ef564 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 8 Jan 2025 15:24:06 -0800 Subject: [PATCH 244/307] try fixing storage delete --- shim/legacy/legacy.go | 74 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 0fb3813fd1..eab0fb69aa 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -46,6 +46,10 @@ func getAccountRoot(tr *trie.Trie, accHash common.Hash) (common.Hash, error) { } func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { + // Collect all nodes that are modified during the update + // Defined here so we can process storage deletes + nodes := trienode.NewMergedNodeSet() + accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) if err != nil { return common.Hash{}, err @@ -58,8 +62,27 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { accHash := common.BytesToHash(kv.Key[:32]) if len(kv.Key) == 32 { if len(kv.Value) == 0 { - // this trie is DELETED, so if it was updated before these updates should not be applied - // further updates shold apply to an empty trie + // this trie is DELETED. First we will remove it from storage: + // if it was updated before, we should have a trie for it + prevRoot, err := getAccountRoot(accounts, accHash) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to get account root %x: %w", accHash, err) + } + if prevRoot != types.EmptyRootHash { + fmt.Printf(":: slow delete storage for %x\n", accHash) + _, _, _, set, err := slowDeleteStorage(l.triedb, l.root, accHash, prevRoot) + if err != nil { + return common.Hash{}, err + } + if set != nil { + updates, deletes := set.Size() + fmt.Printf("::: set merged: %d updates, %d deletes\n", updates, deletes) + nodes.Merge(set) + } + } + + // Also any pending updates should not be apply + // Further updates shold apply to an empty trie tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) if err != nil { return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) @@ -88,7 +111,6 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } // Hash the storage tries - nodes := trienode.NewMergedNodeSet() for _, tr := range tries { _, set, err := tr.Commit(false) if err != nil { @@ -141,3 +163,49 @@ func (l *Legacy) Close() error { return nil } func (l *Legacy) Get(key []byte) ([]byte, error) { panic("implement me") } func (l *Legacy) Prefetch(key []byte) ([]byte, error) { panic("implement me") } func (l *Legacy) Root() common.Hash { return l.root } + +const ( + // storageDeleteLimit denotes the highest permissible memory allocation + // employed for contract storage deletion. + storageDeleteLimit = 512 * 1024 * 1024 +) + +// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage," +// employed when the associated state snapshot is not available. It iterates the +// storage slots along with all internal trie nodes via trie directly. +func slowDeleteStorage( + db *triedb.Database, originalRoot, addrHash, root common.Hash, +) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + tr, err := trie.New(trie.StorageTrieID(originalRoot, addrHash, root), db) + if err != nil { + return false, 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) + } + it, err := tr.NodeIterator(nil) + if err != nil { + return false, 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err) + } + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + for it.Next(true) { + if size > storageDeleteLimit { + return true, size, nil, nil, nil + } + if it.Leaf() { + slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob()) + size += common.StorageSize(common.HashLength + len(it.LeafBlob())) + continue + } + if it.Hash() == (common.Hash{}) { + continue + } + size += common.StorageSize(len(it.Path())) + nodes.AddNode(it.Path(), trienode.NewDeleted()) + } + if err := it.Error(); err != nil { + return false, 0, nil, nil, err + } + return false, size, slots, nodes, nil +} From f03ac770a233ee07fb6f9fffaecb34ef1914f1fb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 9 Jan 2025 15:29:49 -0800 Subject: [PATCH 245/307] write slim rlp snapshot --- plugin/evm/post_processing_test.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 116f5dac0b..873ad6de8d 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -12,10 +12,12 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state/snapshot" + "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/shim/legacy" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" lru "github.com/hashicorp/golang-lru/v2" "github.com/maypok86/otter" "github.com/stretchr/testify/require" @@ -413,17 +415,26 @@ func TestPostProcess(t *testing.T) { if writeSnapshot { for _, kv := range evitcedBatch { if len(kv.Key) == 32 { - rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), kv.Value) if len(kv.Value) == 0 { - it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) - for it.Next() { - k := it.Key()[len(rawdb.SnapshotStoragePrefix):] - rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) + rawdb.DeleteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key)) + } else { + var acc types.StateAccount + if err := rlp.DecodeBytes(kv.Value, &acc); err != nil { + t.Fatalf("Failed to decode account: %v", err) } - if err := it.Error(); err != nil { - t.Fatalf("Failed to iterate over snapshot account: %v", err) + data := types.SlimAccountRLP(acc) + rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), data) + if len(kv.Value) == 0 { + it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) + for it.Next() { + k := it.Key()[len(rawdb.SnapshotStoragePrefix):] + rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) + } + if err := it.Error(); err != nil { + t.Fatalf("Failed to iterate over snapshot account: %v", err) + } + it.Release() } - it.Release() } } else { rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) From d604e23023b1314aea1135d7a134280483978b7c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 9 Jan 2025 15:34:06 -0800 Subject: [PATCH 246/307] fix snapshot delete handling --- plugin/evm/post_processing_test.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 873ad6de8d..17c0b3f680 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -417,6 +417,15 @@ func TestPostProcess(t *testing.T) { if len(kv.Key) == 32 { if len(kv.Value) == 0 { rawdb.DeleteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key)) + it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) + for it.Next() { + k := it.Key()[len(rawdb.SnapshotStoragePrefix):] + rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) + } + if err := it.Error(); err != nil { + t.Fatalf("Failed to iterate over snapshot account: %v", err) + } + it.Release() } else { var acc types.StateAccount if err := rlp.DecodeBytes(kv.Value, &acc); err != nil { @@ -424,17 +433,6 @@ func TestPostProcess(t *testing.T) { } data := types.SlimAccountRLP(acc) rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), data) - if len(kv.Value) == 0 { - it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) - for it.Next() { - k := it.Key()[len(rawdb.SnapshotStoragePrefix):] - rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) - } - if err := it.Error(); err != nil { - t.Fatalf("Failed to iterate over snapshot account: %v", err) - } - it.Release() - } } } else { rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) From daaa145d6369f9e8e02d93cbab943e710586f4b7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 9 Jan 2025 16:09:35 -0800 Subject: [PATCH 247/307] pass-through node It --- shim/trie.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shim/trie.go b/shim/trie.go index e0a150617e..8a2377dc8e 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -103,6 +103,12 @@ func (t *Trie) Copy() *Trie { return t } -func (t *Trie) NodeIterator(start []byte) (trie.NodeIterator, error) { panic("not implemented") } +func (t *Trie) NodeIterator(start []byte) (trie.NodeIterator, error) { + if legacy, ok := t.backend.(*LegacyBackend); ok { + return legacy.tr.NodeIterator(start) + } + panic("not implemented") +} + func (t *Trie) MustNodeIterator(start []byte) trie.NodeIterator { panic("not implemented") } func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { panic("not implemented") } From 5e3b97eaf07179fe298577c45451d18621d09244 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 9 Jan 2025 16:19:23 -0800 Subject: [PATCH 248/307] add snapshot del log --- plugin/evm/post_processing_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 17c0b3f680..653eab8691 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -418,14 +418,19 @@ func TestPostProcess(t *testing.T) { if len(kv.Value) == 0 { rawdb.DeleteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key)) it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) + keysDeleted := 0 for it.Next() { k := it.Key()[len(rawdb.SnapshotStoragePrefix):] rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) + keysDeleted++ } if err := it.Error(); err != nil { t.Fatalf("Failed to iterate over snapshot account: %v", err) } it.Release() + if keysDeleted > 0 { + t.Logf("Deleted %d storage keys for account %x", keysDeleted, kv.Key) + } } else { var acc types.StateAccount if err := rlp.DecodeBytes(kv.Value, &acc); err != nil { From 235d1c1b16aab93a37a021d5e30c40174960e444 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 06:10:23 -0800 Subject: [PATCH 249/307] nit --- shim/legacy/legacy.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index eab0fb69aa..c84229107c 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -80,6 +80,8 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { nodes.Merge(set) } } + // Remove the account from the account trie so we don't try to delete it again + accounts.MustDelete(kv.Key[:32]) // Also any pending updates should not be apply // Further updates shold apply to an empty trie From 8f880d53d44dac48e69666322e10f89551238dd1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 06:12:15 -0800 Subject: [PATCH 250/307] track leaf dels --- shim/legacy/legacy.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index c84229107c..59c98ecca4 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -70,13 +70,13 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } if prevRoot != types.EmptyRootHash { fmt.Printf(":: slow delete storage for %x\n", accHash) - _, _, _, set, err := slowDeleteStorage(l.triedb, l.root, accHash, prevRoot) + _, leafs, _, set, err := slowDeleteStorage(l.triedb, l.root, accHash, prevRoot) if err != nil { return common.Hash{}, err } if set != nil { updates, deletes := set.Size() - fmt.Printf("::: set merged: %d updates, %d deletes\n", updates, deletes) + fmt.Printf("::: set merged: %d updates, %d deletes (%d leafs deleted)\n", updates, deletes, leafs) nodes.Merge(set) } } @@ -177,7 +177,7 @@ const ( // storage slots along with all internal trie nodes via trie directly. func slowDeleteStorage( db *triedb.Database, originalRoot, addrHash, root common.Hash, -) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { +) (bool, int, map[common.Hash][]byte, *trienode.NodeSet, error) { tr, err := trie.New(trie.StorageTrieID(originalRoot, addrHash, root), db) if err != nil { return false, 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) @@ -188,16 +188,18 @@ func slowDeleteStorage( } var ( size common.StorageSize + leafs int nodes = trienode.NewNodeSet(addrHash) slots = make(map[common.Hash][]byte) ) for it.Next(true) { if size > storageDeleteLimit { - return true, size, nil, nil, nil + return true, leafs, nil, nil, nil } if it.Leaf() { slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob()) size += common.StorageSize(common.HashLength + len(it.LeafBlob())) + leafs++ continue } if it.Hash() == (common.Hash{}) { @@ -209,5 +211,5 @@ func slowDeleteStorage( if err := it.Error(); err != nil { return false, 0, nil, nil, err } - return false, size, slots, nodes, nil + return false, leafs, slots, nodes, nil } From d729a6b258931782f6bf59616fc21f4dde6041e7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 06:23:44 -0800 Subject: [PATCH 251/307] add more logs --- shim/legacy/legacy.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 59c98ecca4..4ed913e21d 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -83,6 +83,17 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { // Remove the account from the account trie so we don't try to delete it again accounts.MustDelete(kv.Key[:32]) + if pending, ok := tries[accHash]; ok { + // If there are pending updates for this account, we should not apply them + // but log them for checking + nodeIt := pending.MustNodeIterator(nil) + it := trie.NewIterator(nodeIt) + keys := 0 + for it.Next() { + keys++ + } + fmt.Printf("::: dropped trie with %d pending updates for %x\n", keys, accHash) + } // Also any pending updates should not be apply // Further updates shold apply to an empty trie tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) From b792779870e93c490a5bfbcdaaeecbc7a7d343f7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 06:26:52 -0800 Subject: [PATCH 252/307] nit --- shim/legacy/legacy.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 4ed913e21d..87a258be8b 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -93,12 +93,12 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { keys++ } fmt.Printf("::: dropped trie with %d pending updates for %x\n", keys, accHash) - } - // Also any pending updates should not be apply - // Further updates shold apply to an empty trie - tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) - if err != nil { - return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) + // Also any pending updates should not be apply + // Further updates shold apply to an empty trie + tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) + } } } From 309e4530abdfefcf362b62f82bbe259b9693da2d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 09:41:54 -0800 Subject: [PATCH 253/307] try --- plugin/evm/post_processing_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 653eab8691..9459519ddb 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -440,7 +440,11 @@ func TestPostProcess(t *testing.T) { rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), data) } } else { - rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) + if len(kv.Value) > 0 { + rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) + } else { + rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:])) + } } } From d8313bc185ac9834242f86011223aae6bec101af Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 10:57:17 -0800 Subject: [PATCH 254/307] update iface --- plugin/evm/post_processing_test.go | 60 ++++++++++++++------------ plugin/evm/reprocess_recording_test.go | 13 +++--- plugin/evm/reprocess_test.go | 2 +- shim/kv_backend.go | 22 +++++++--- shim/legacy/legacy.go | 53 ++++++++++++----------- shim/merkledb/merkledb.go | 29 ++++++------- shim/nomt/nomt.go | 24 +++++------ triedb/database.go | 26 +++++------ 8 files changed, 123 insertions(+), 106 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 9459519ddb..d6ac06c9fc 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -186,14 +186,15 @@ func TestPostProcess(t *testing.T) { } var ( - dbs dbs - sourceDb ethdb.Database - sum totals - blockNumber uint64 - storageRoot common.Hash - storage triedb.KVBackend - evitcedBatch triedb.Batch - lastCommit struct { + dbs dbs + sourceDb ethdb.Database + sum totals + blockNumber uint64 + storageRoot common.Hash + storage triedb.KVBackend + evictedKs [][]byte + evictedVs [][]byte + lastCommit struct { txs uint64 number uint64 } @@ -246,7 +247,8 @@ func TestPostProcess(t *testing.T) { } // t.Logf("evicting key: %x @ block %d, updatedAt: %d (%d blocks ago)", short(k), blockNumber, v.updatedAt, blockNumber-v.updatedAt) if storage != nil { - evitcedBatch = append(evitcedBatch, triedb.KV{Key: []byte(k), Value: v.val}) + evictedKs = append(evictedKs, []byte(k)) + evictedVs = append(evictedVs, v.val) } sum.writeCacheEvictTime += time.Since(now) } @@ -314,11 +316,11 @@ func TestPostProcess(t *testing.T) { accountDeletes, storageDeletes := 0, 0 // 1. Read account writes from the tape as they come first - accountWritesBatch := make([]triedb.KV, accountWrites) + accountWritesBatch := make([]KV, accountWrites) for j := 0; j < int(accountWrites); j++ { k, v, err := readKV(r, 32) require.NoError(t, err) - accountWritesBatch[j] = triedb.KV{Key: k, Value: v} + accountWritesBatch[j] = KV{Key: k, Value: v} } // 2. Process storage writes @@ -391,20 +393,21 @@ func TestPostProcess(t *testing.T) { shouldCommitBlocks := commitEachBlocks > 0 && blockNumber-lastCommit.number >= uint64(commitEachBlocks) shouldCommitTxs := commitEachTxs > 0 && sum.txs+sum.atomicTxs-lastCommit.txs >= uint64(commitEachTxs) - if len(evitcedBatch) > 0 && (shouldCommitBlocks || shouldCommitTxs) { + if len(evictedKs) > 0 && (shouldCommitBlocks || shouldCommitTxs) { if tapeVerbose { - for _, kv := range evitcedBatch { - t.Logf("storing: %x -> %x", kv.Key, kv.Value) + for i, k := range evictedKs { + t.Logf("storing: %x -> %x", k, evictedVs[i]) } } now := time.Now() // Get state commitment from storage backend - storageRoot, err = storage.Update(evitcedBatch) + storageRootBytes, err := storage.Update(evictedKs, evictedVs) require.NoError(t, err) + storageRoot = common.BytesToHash(storageRootBytes) updateTime := time.Since(now) // Request storage backend to persist the state - err = storage.Commit(storageRoot) + err = storage.Commit(storageRootBytes) require.NoError(t, err) sum.storagePersistTime += time.Since(now) - updateTime @@ -413,11 +416,12 @@ func TestPostProcess(t *testing.T) { sum.storageUpdateCount++ if writeSnapshot { - for _, kv := range evitcedBatch { - if len(kv.Key) == 32 { - if len(kv.Value) == 0 { - rawdb.DeleteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key)) - it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, kv.Key...), nil) + for i, k := range evictedKs { + v := evictedVs[i] + if len(k) == 32 { + if len(v) == 0 { + rawdb.DeleteAccountSnapshot(dbs.chain, common.BytesToHash(k)) + it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, k...), nil) keysDeleted := 0 for it.Next() { k := it.Key()[len(rawdb.SnapshotStoragePrefix):] @@ -429,26 +433,26 @@ func TestPostProcess(t *testing.T) { } it.Release() if keysDeleted > 0 { - t.Logf("Deleted %d storage keys for account %x", keysDeleted, kv.Key) + t.Logf("Deleted %d storage keys for account %x", keysDeleted, k) } } else { var acc types.StateAccount - if err := rlp.DecodeBytes(kv.Value, &acc); err != nil { + if err := rlp.DecodeBytes(v, &acc); err != nil { t.Fatalf("Failed to decode account: %v", err) } data := types.SlimAccountRLP(acc) - rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(kv.Key), data) + rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(k), data) } } else { - if len(kv.Value) > 0 { - rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:]), kv.Value) + if len(v) > 0 { + rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:]), v) } else { - rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(kv.Key[:32]), common.BytesToHash(kv.Key[32:])) + rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) } } } - evitcedBatch = evitcedBatch[:0] + evictedKs, evictedVs = evictedKs[:0], evictedVs[:0] } if sourceDb != nil { // update block and metadata from source db diff --git a/plugin/evm/reprocess_recording_test.go b/plugin/evm/reprocess_recording_test.go index 3f796a9bf2..427f63a949 100644 --- a/plugin/evm/reprocess_recording_test.go +++ b/plugin/evm/reprocess_recording_test.go @@ -8,7 +8,6 @@ import ( "os" "github.com/ava-labs/coreth/core/types" - "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" ) @@ -42,14 +41,18 @@ import ( // - Key hash (32 bytes) // - Value len (byte) +type KV struct { + Key []byte + Value []byte +} type blockRecorder struct { accountReads int storageReads int txEnds int readTape []byte - accountWrites []triedb.KV - storageWrites []triedb.KV + accountWrites []KV + storageWrites []KV fileManager *fileManager } @@ -57,9 +60,9 @@ type blockRecorder struct { func (b *blockRecorder) MustUpdate(key, value []byte) { switch len(key) { case 32: - b.accountWrites = append(b.accountWrites, triedb.KV{Key: key, Value: value}) + b.accountWrites = append(b.accountWrites, KV{Key: key, Value: value}) case 64: - b.storageWrites = append(b.storageWrites, triedb.KV{Key: key, Value: value}) + b.storageWrites = append(b.storageWrites, KV{Key: key, Value: value}) default: panic("unexpected key length") } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index de46c712cc..35be373573 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -502,7 +502,7 @@ func reprocess( lastRoot = normalGenesis.Root() if backend := cacheConfig.KeyValueDB.KVBackend; backend != nil { - lastRoot = backend.Root() + lastRoot = common.BytesToHash(backend.Root()) } t.Logf("Genesis performed: hash: %x, root : %x", bc.CurrentBlock().Hash(), lastRoot) diff --git a/shim/kv_backend.go b/shim/kv_backend.go index c089d1d037..9166507fac 100644 --- a/shim/kv_backend.go +++ b/shim/kv_backend.go @@ -17,11 +17,16 @@ var ( ) type ( - KV = triedb.KV - Batch = triedb.Batch KVBackend = triedb.KVBackend ) +type KV struct { + Key []byte + Value []byte +} + +type Batch []KV + type KVTrieBackend struct { hashed bool hash common.Hash @@ -46,13 +51,18 @@ func (k *KVTrieBackend) Hash(batch Batch) common.Hash { //for _, kv := range batch { // fmt.Printf("Update: %x %x\n", kv.Key, kv.Value) //} - root, err := k.backend.Update(batch) + ks, vs := make([][]byte, len(batch)), make([][]byte, len(batch)) + for i, kv := range batch { + ks[i] = kv.Key + vs[i] = kv.Value + } + root, err := k.backend.Update(ks, vs) if err != nil { panic(fmt.Sprintf("failed to update trie: %v", err)) } k.hashed = true - k.hash = root - return root + k.hash = common.BytesToHash(root) + return k.hash } func (k *KVTrieBackend) Commit(batch Batch, collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { @@ -68,7 +78,7 @@ func NewAccountTrieKV(stateRoot common.Hash, kv KVBackend, db database.Database) if stateRoot == types.EmptyRootHash { stateRoot = common.Hash{} } - kvRoot := kv.Root() + kvRoot := common.BytesToHash(kv.Root()) if kvRoot != stateRoot { return nil, fmt.Errorf("%w: expected %x, got %x", ErrRootMismatch, stateRoot, kvRoot) } diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 87a258be8b..4246a1ea38 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -45,34 +45,35 @@ func getAccountRoot(tr *trie.Trie, accHash common.Hash) (common.Hash, error) { return root, nil } -func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { +func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { // Collect all nodes that are modified during the update // Defined here so we can process storage deletes nodes := trienode.NewMergedNodeSet() accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) if err != nil { - return common.Hash{}, err + return nil, err } // Process the storage tries first, this means we can access the root for the // storage tries before they are updated in the account trie. Necessary for // the hash scheme. tries := make(map[common.Hash]*trie.Trie) - for _, kv := range batch { - accHash := common.BytesToHash(kv.Key[:32]) - if len(kv.Key) == 32 { - if len(kv.Value) == 0 { + for i, k := range ks { + v := vs[i] + accHash := common.BytesToHash(k[:32]) + if len(k) == 32 { + if len(v) == 0 { // this trie is DELETED. First we will remove it from storage: // if it was updated before, we should have a trie for it prevRoot, err := getAccountRoot(accounts, accHash) if err != nil { - return common.Hash{}, fmt.Errorf("failed to get account root %x: %w", accHash, err) + return nil, fmt.Errorf("failed to get account root %x: %w", accHash, err) } if prevRoot != types.EmptyRootHash { fmt.Printf(":: slow delete storage for %x\n", accHash) _, leafs, _, set, err := slowDeleteStorage(l.triedb, l.root, accHash, prevRoot) if err != nil { - return common.Hash{}, err + return nil, err } if set != nil { updates, deletes := set.Size() @@ -81,7 +82,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } } // Remove the account from the account trie so we don't try to delete it again - accounts.MustDelete(kv.Key[:32]) + accounts.MustDelete(k[:32]) if pending, ok := tries[accHash]; ok { // If there are pending updates for this account, we should not apply them @@ -97,7 +98,7 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { // Further updates shold apply to an empty trie tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) if err != nil { - return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) + return nil, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) } } } @@ -110,24 +111,24 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { if !ok { root, err := getAccountRoot(accounts, accHash) if err != nil { - return common.Hash{}, err + return nil, err } tr, err = trie.New(trie.StorageTrieID(l.root, accHash, root), l.triedb) if err != nil { - return common.Hash{}, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) + return nil, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) } tries[accHash] = tr } // Update the storage trie - tr.MustUpdate(kv.Key[32:], kv.Value) + tr.MustUpdate(k[32:], v) } // Hash the storage tries for _, tr := range tries { _, set, err := tr.Commit(false) if err != nil { - return common.Hash{}, err + return nil, err } if set != nil { nodes.Merge(set) @@ -135,47 +136,51 @@ func (l *Legacy) Update(batch triedb.Batch) (common.Hash, error) { } // Update the account trie - for _, kv := range batch { - if len(kv.Key) == 64 { + for i, k := range ks { + v := vs[i] + if len(k) == 64 { continue } - accounts.MustUpdate(kv.Key, kv.Value) + accounts.MustUpdate(k, v) } // Verify account trie updates match the storage trie updates for accHash, tr := range tries { root, err := getAccountRoot(accounts, accHash) if err != nil { - return common.Hash{}, err + return nil, err } if root != tr.Hash() { - return common.Hash{}, fmt.Errorf("account %x trie root mismatch (%x != %x)", accHash, root, tr.Hash()) + return nil, fmt.Errorf("account %x trie root mismatch (%x != %x)", accHash, root, tr.Hash()) } } next, set, err := accounts.Commit(true) if err != nil { - return common.Hash{}, err + return nil, err } if set != nil { nodes.Merge(set) } if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { - return common.Hash{}, err + return nil, err } // TODO: fix hashdb scheme later l.root = next l.count++ - return next, nil + return next[:], nil } -func (l *Legacy) Commit(root common.Hash) error { return l.triedb.Commit(root, false) } +func (l *Legacy) Commit(rootBytes []byte) error { + root := common.BytesToHash(rootBytes) + return l.triedb.Commit(root, false) +} func (l *Legacy) Close() error { return nil } func (l *Legacy) Get(key []byte) ([]byte, error) { panic("implement me") } func (l *Legacy) Prefetch(key []byte) ([]byte, error) { panic("implement me") } -func (l *Legacy) Root() common.Hash { return l.root } +func (l *Legacy) Root() []byte { return l.root[:] } const ( // storageDeleteLimit denotes the highest permissible memory allocation diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index d0f600b175..4a20bec59b 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -50,36 +50,35 @@ func (m *MerkleDB) latestViewLocked() merkledb.Trie { return m.pendingViews[len(m.pendingViews)-1] } -func (m *MerkleDB) Update(batch triedb.Batch) (common.Hash, error) { +func (m *MerkleDB) Update(ks, vs [][]byte) ([]byte, error) { m.lock.Lock() defer m.lock.Unlock() ctx := context.TODO() - changes := make([]database.BatchOp, len(batch)) - for i, kv := range batch { - changes[i] = database.BatchOp{ - Key: kv.Key, - Value: kv.Value, - Delete: len(kv.Value) == 0, - } + changes := make([]database.BatchOp, len(ks)) + for i, k := range ks { + v := vs[i] + changes[i] = database.BatchOp{Key: k, Value: v, Delete: len(v) == 0} } view, err := m.latestViewLocked().NewView(ctx, merkledb.ViewChanges{BatchOps: changes}) if err != nil { - return common.Hash{}, err + return nil, err } root, err := view.GetMerkleRoot(ctx) if err != nil { - return common.Hash{}, err + return nil, err } m.pendingViews = append(m.pendingViews, view) m.pendingViewRoots = append(m.pendingViewRoots, common.Hash(root)) - return common.Hash(root), nil + return root[:], nil } -func (m *MerkleDB) Commit(root common.Hash) error { +func (m *MerkleDB) Commit(rootBytes []byte) error { m.lock.Lock() defer m.lock.Unlock() + root := common.BytesToHash(rootBytes) + if len(m.pendingViews) == 0 { return fmt.Errorf("no pending views") } @@ -114,13 +113,13 @@ func (m *MerkleDB) commitToDisk(root common.Hash) error { return nil } -func (m *MerkleDB) Root() common.Hash { +func (m *MerkleDB) Root() []byte { ctx := context.TODO() root, err := m.latestView().GetMerkleRoot(ctx) if err != nil { panic(fmt.Sprintf("failed to get merkle root: %v", err)) } - return common.Hash(root) + return root[:] } func (m *MerkleDB) Close() error { @@ -132,7 +131,7 @@ func (m *MerkleDB) Close() error { m.lock.Unlock() if last != (common.Hash{}) { - if err := m.Commit(last); err != nil { + if err := m.Commit(last[:]); err != nil { return err } } diff --git a/shim/nomt/nomt.go b/shim/nomt/nomt.go index e6d758d367..b6e4884b9e 100644 --- a/shim/nomt/nomt.go +++ b/shim/nomt/nomt.go @@ -7,7 +7,6 @@ import ( "github.com/ava-labs/coreth/shim/nomt/nomt" "github.com/ava-labs/coreth/triedb" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "google.golang.org/protobuf/proto" ) @@ -58,7 +57,7 @@ func (n *Nomt) response(req *nomt.Request) (*nomt.Response, error) { return response(n.conn, req) } -func (n *Nomt) Root() common.Hash { +func (n *Nomt) Root() []byte { req := &nomt.Request{ Request: &nomt.Request_Root{ Root: &nomt.RootRequest{}, @@ -67,9 +66,9 @@ func (n *Nomt) Root() common.Hash { resp, err := n.response(req) if err != nil { log.Error("Failed to get root", "err", err) - return common.Hash{} + return nil } - return common.BytesToHash(resp.GetRoot().Root) + return resp.GetRoot().Root } func (n *Nomt) Get(key []byte) ([]byte, error) { @@ -104,30 +103,27 @@ func (n *Nomt) Prefetch(key []byte) ([]byte, error) { return nil, err } -func (n *Nomt) Update(batch triedb.Batch) (common.Hash, error) { +func (n *Nomt) Update(ks, vs [][]byte) ([]byte, error) { req := &nomt.Request{ Request: &nomt.Request_Update{ Update: &nomt.UpdateRequest{ - Items: make([]*nomt.UpdateRequestItem, len(batch)), + Items: make([]*nomt.UpdateRequestItem, len(ks)), }, }, } - for i, item := range batch { - req.GetUpdate().Items[i] = &nomt.UpdateRequestItem{ - Key: item.Key, - Value: item.Value, - } + for i, k := range ks { + req.GetUpdate().Items[i] = &nomt.UpdateRequestItem{Key: k, Value: vs[i]} } resp, err := n.response(req) if err != nil { - return common.Hash{}, err + return nil, err } - return common.BytesToHash(resp.GetUpdate().Root), nil + return resp.GetUpdate().Root, nil } -func (n *Nomt) Commit(root common.Hash) error { +func (n *Nomt) Commit(root []byte) error { return nil } diff --git a/triedb/database.go b/triedb/database.go index 9cb7405aec..6af46ea97e 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -17,6 +17,7 @@ package triedb import ( + "bytes" "errors" "github.com/ava-labs/coreth/trie" @@ -30,17 +31,11 @@ import ( "github.com/ethereum/go-ethereum/log" ) -type KV struct { - Key []byte - Value []byte -} - -type Batch []KV - type KVBackend interface { // Returns the current root hash of the trie. // Empty trie must return common.Hash{}. - Root() common.Hash + // Length of the returned slice must be common.HashLength. + Root() []byte // Get retrieves the value for the given key. // If the key does not exist, it must return (nil, nil). @@ -51,15 +46,20 @@ type KVBackend interface { Prefetch(key []byte) ([]byte, error) // After this call, Root() should return the same hash as returned by this call. - // Note when len(Value) == 0, it means the key should be deleted. + // Note when length of a particular value, it means the corresponding key should + // be deleted. + // There may be duplicate keys in the batch provided, and the last one should + // take effect. // Note after this call, the next call to Update must build on the returned root, // regardless of whether Commit is called. - Update(Batch) (common.Hash, error) + // Length of the returned root must be common.HashLength. + Update(keys, vals [][]byte) ([]byte, error) // After this call, changes related to [root] should be persisted to disk. // This may be implemented as no-op if Update already persists changes, or // commits happen on a rolling basis. - Commit(root common.Hash) error + // Length of the root slice is guaranteed to be common.HashLength. + Commit(root []byte) error // Close closes the backend and releases all held resources. Close() error @@ -210,7 +210,7 @@ func (db *Database) Commit(root common.Hash, report bool) error { } if db.config.KeyValueDB != nil { if backend := db.config.KeyValueDB.KVBackend; backend != nil { - return backend.Commit(root) + return backend.Commit(root[:]) } } return db.backend.Commit(root, report) @@ -236,7 +236,7 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize, common.Stora func (db *Database) Initialized(genesisRoot common.Hash) bool { if db.config.KeyValueDB != nil { if backend := db.config.KeyValueDB.KVBackend; backend != nil { - return backend.Root() != common.Hash{} + return !bytes.Equal(backend.Root(), common.Hash{}.Bytes()) } } return db.backend.Initialized(genesisRoot) From 316c5468843e7ffe2a921488d84b97ec69c95d25 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 10:57:57 -0800 Subject: [PATCH 255/307] add generated file just in case --- shim/nomt/nomt/message.pb.go | 1082 ++++++++++++++++++++++++++++++++++ 1 file changed, 1082 insertions(+) create mode 100644 shim/nomt/nomt/message.pb.go diff --git a/shim/nomt/nomt/message.pb.go b/shim/nomt/nomt/message.pb.go new file mode 100644 index 0000000000..078d61137d --- /dev/null +++ b/shim/nomt/nomt/message.pb.go @@ -0,0 +1,1082 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc v5.29.2 +// source: message.proto + +package nomt + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Request struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Request: + // + // *Request_Root + // *Request_Get + // *Request_Prefetch + // *Request_Update + // *Request_Close + Request isRequest_Request `protobuf_oneof:"request"` +} + +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{0} +} + +func (m *Request) GetRequest() isRequest_Request { + if m != nil { + return m.Request + } + return nil +} + +func (x *Request) GetRoot() *RootRequest { + if x, ok := x.GetRequest().(*Request_Root); ok { + return x.Root + } + return nil +} + +func (x *Request) GetGet() *GetRequest { + if x, ok := x.GetRequest().(*Request_Get); ok { + return x.Get + } + return nil +} + +func (x *Request) GetPrefetch() *PrefetchRequest { + if x, ok := x.GetRequest().(*Request_Prefetch); ok { + return x.Prefetch + } + return nil +} + +func (x *Request) GetUpdate() *UpdateRequest { + if x, ok := x.GetRequest().(*Request_Update); ok { + return x.Update + } + return nil +} + +func (x *Request) GetClose() *CloseRequest { + if x, ok := x.GetRequest().(*Request_Close); ok { + return x.Close + } + return nil +} + +type isRequest_Request interface { + isRequest_Request() +} + +type Request_Root struct { + Root *RootRequest `protobuf:"bytes,1,opt,name=root,proto3,oneof"` +} + +type Request_Get struct { + Get *GetRequest `protobuf:"bytes,2,opt,name=get,proto3,oneof"` +} + +type Request_Prefetch struct { + Prefetch *PrefetchRequest `protobuf:"bytes,3,opt,name=prefetch,proto3,oneof"` +} + +type Request_Update struct { + Update *UpdateRequest `protobuf:"bytes,4,opt,name=update,proto3,oneof"` +} + +type Request_Close struct { + Close *CloseRequest `protobuf:"bytes,5,opt,name=close,proto3,oneof"` +} + +func (*Request_Root) isRequest_Request() {} + +func (*Request_Get) isRequest_Request() {} + +func (*Request_Prefetch) isRequest_Request() {} + +func (*Request_Update) isRequest_Request() {} + +func (*Request_Close) isRequest_Request() {} + +type Response struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ErrCode int32 `protobuf:"varint,1,opt,name=err_code,json=errCode,proto3" json:"err_code,omitempty"` + // Types that are assignable to Response: + // + // *Response_Root + // *Response_Get + // *Response_Prefetch + // *Response_Update + // *Response_Close + Response isResponse_Response `protobuf_oneof:"response"` +} + +func (x *Response) Reset() { + *x = Response{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{1} +} + +func (x *Response) GetErrCode() int32 { + if x != nil { + return x.ErrCode + } + return 0 +} + +func (m *Response) GetResponse() isResponse_Response { + if m != nil { + return m.Response + } + return nil +} + +func (x *Response) GetRoot() *RootResponse { + if x, ok := x.GetResponse().(*Response_Root); ok { + return x.Root + } + return nil +} + +func (x *Response) GetGet() *GetResponse { + if x, ok := x.GetResponse().(*Response_Get); ok { + return x.Get + } + return nil +} + +func (x *Response) GetPrefetch() *PrefetchResponse { + if x, ok := x.GetResponse().(*Response_Prefetch); ok { + return x.Prefetch + } + return nil +} + +func (x *Response) GetUpdate() *UpdateResponse { + if x, ok := x.GetResponse().(*Response_Update); ok { + return x.Update + } + return nil +} + +func (x *Response) GetClose() *CloseResponse { + if x, ok := x.GetResponse().(*Response_Close); ok { + return x.Close + } + return nil +} + +type isResponse_Response interface { + isResponse_Response() +} + +type Response_Root struct { + Root *RootResponse `protobuf:"bytes,2,opt,name=root,proto3,oneof"` +} + +type Response_Get struct { + Get *GetResponse `protobuf:"bytes,3,opt,name=get,proto3,oneof"` +} + +type Response_Prefetch struct { + Prefetch *PrefetchResponse `protobuf:"bytes,4,opt,name=prefetch,proto3,oneof"` +} + +type Response_Update struct { + Update *UpdateResponse `protobuf:"bytes,5,opt,name=update,proto3,oneof"` +} + +type Response_Close struct { + Close *CloseResponse `protobuf:"bytes,6,opt,name=close,proto3,oneof"` +} + +func (*Response_Root) isResponse_Response() {} + +func (*Response_Get) isResponse_Response() {} + +func (*Response_Prefetch) isResponse_Response() {} + +func (*Response_Update) isResponse_Response() {} + +func (*Response_Close) isResponse_Response() {} + +type RootRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RootRequest) Reset() { + *x = RootRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RootRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RootRequest) ProtoMessage() {} + +func (x *RootRequest) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RootRequest.ProtoReflect.Descriptor instead. +func (*RootRequest) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{2} +} + +type RootResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Root []byte `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` +} + +func (x *RootResponse) Reset() { + *x = RootResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RootResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RootResponse) ProtoMessage() {} + +func (x *RootResponse) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RootResponse.ProtoReflect.Descriptor instead. +func (*RootResponse) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{3} +} + +func (x *RootResponse) GetRoot() []byte { + if x != nil { + return x.Root + } + return nil +} + +type GetRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *GetRequest) Reset() { + *x = GetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRequest) ProtoMessage() {} + +func (x *GetRequest) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRequest.ProtoReflect.Descriptor instead. +func (*GetRequest) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{4} +} + +func (x *GetRequest) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +type GetResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *GetResponse) Reset() { + *x = GetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResponse) ProtoMessage() {} + +func (x *GetResponse) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. +func (*GetResponse) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{5} +} + +func (x *GetResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type PrefetchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *PrefetchRequest) Reset() { + *x = PrefetchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrefetchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrefetchRequest) ProtoMessage() {} + +func (x *PrefetchRequest) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrefetchRequest.ProtoReflect.Descriptor instead. +func (*PrefetchRequest) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{6} +} + +func (x *PrefetchRequest) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +type PrefetchResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PrefetchResponse) Reset() { + *x = PrefetchResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrefetchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrefetchResponse) ProtoMessage() {} + +func (x *PrefetchResponse) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrefetchResponse.ProtoReflect.Descriptor instead. +func (*PrefetchResponse) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{7} +} + +type UpdateRequestItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *UpdateRequestItem) Reset() { + *x = UpdateRequestItem{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRequestItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequestItem) ProtoMessage() {} + +func (x *UpdateRequestItem) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequestItem.ProtoReflect.Descriptor instead. +func (*UpdateRequestItem) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{8} +} + +func (x *UpdateRequestItem) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *UpdateRequestItem) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type UpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*UpdateRequestItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *UpdateRequest) Reset() { + *x = UpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequest) ProtoMessage() {} + +func (x *UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateRequest) GetItems() []*UpdateRequestItem { + if x != nil { + return x.Items + } + return nil +} + +type UpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Root []byte `protobuf:"bytes,2,opt,name=root,proto3" json:"root,omitempty"` +} + +func (x *UpdateResponse) Reset() { + *x = UpdateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateResponse) ProtoMessage() {} + +func (x *UpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateResponse.ProtoReflect.Descriptor instead. +func (*UpdateResponse) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateResponse) GetRoot() []byte { + if x != nil { + return x.Root + } + return nil +} + +type CloseRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CloseRequest) Reset() { + *x = CloseRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseRequest) ProtoMessage() {} + +func (x *CloseRequest) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseRequest.ProtoReflect.Descriptor instead. +func (*CloseRequest) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{11} +} + +type CloseResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CloseResponse) Reset() { + *x = CloseResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseResponse) ProtoMessage() {} + +func (x *CloseResponse) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseResponse.ProtoReflect.Descriptor instead. +func (*CloseResponse) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{12} +} + +var File_message_proto protoreflect.FileDescriptor + +var file_message_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x12, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x63, 0x65, 0x22, 0xb9, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x35, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, + 0x63, 0x65, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x32, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x03, 0x67, 0x65, 0x74, 0x12, 0x41, 0x0a, 0x08, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x2e, 0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x12, 0x3b, 0x0a, + 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, + 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x48, 0x00, 0x52, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, + 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x2e, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x63, + 0x6c, 0x6f, 0x73, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0xdb, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, + 0x65, 0x72, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, + 0x65, 0x72, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x2e, 0x52, 0x6f, 0x6f, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, + 0x33, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, + 0x03, 0x67, 0x65, 0x74, 0x12, 0x42, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x65, 0x66, + 0x65, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x08, + 0x70, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x12, 0x3c, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, + 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x06, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0d, 0x0a, + 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x22, 0x0a, 0x0c, + 0x52, 0x6f, 0x6f, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, + 0x22, 0x1e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x22, 0x23, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x23, 0x0a, 0x0f, 0x50, 0x72, 0x65, 0x66, 0x65, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x12, 0x0a, 0x10, 0x50, 0x72, + 0x65, 0x66, 0x65, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3b, + 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x74, 0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4c, 0x0a, 0x0d, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x24, 0x0a, 0x0e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, + 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x22, + 0x0e, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x0f, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_message_proto_rawDescOnce sync.Once + file_message_proto_rawDescData = file_message_proto_rawDesc +) + +func file_message_proto_rawDescGZIP() []byte { + file_message_proto_rawDescOnce.Do(func() { + file_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_message_proto_rawDescData) + }) + return file_message_proto_rawDescData +} + +var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_message_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: database_interface.Request + (*Response)(nil), // 1: database_interface.Response + (*RootRequest)(nil), // 2: database_interface.RootRequest + (*RootResponse)(nil), // 3: database_interface.RootResponse + (*GetRequest)(nil), // 4: database_interface.GetRequest + (*GetResponse)(nil), // 5: database_interface.GetResponse + (*PrefetchRequest)(nil), // 6: database_interface.PrefetchRequest + (*PrefetchResponse)(nil), // 7: database_interface.PrefetchResponse + (*UpdateRequestItem)(nil), // 8: database_interface.UpdateRequestItem + (*UpdateRequest)(nil), // 9: database_interface.UpdateRequest + (*UpdateResponse)(nil), // 10: database_interface.UpdateResponse + (*CloseRequest)(nil), // 11: database_interface.CloseRequest + (*CloseResponse)(nil), // 12: database_interface.CloseResponse +} +var file_message_proto_depIdxs = []int32{ + 2, // 0: database_interface.Request.root:type_name -> database_interface.RootRequest + 4, // 1: database_interface.Request.get:type_name -> database_interface.GetRequest + 6, // 2: database_interface.Request.prefetch:type_name -> database_interface.PrefetchRequest + 9, // 3: database_interface.Request.update:type_name -> database_interface.UpdateRequest + 11, // 4: database_interface.Request.close:type_name -> database_interface.CloseRequest + 3, // 5: database_interface.Response.root:type_name -> database_interface.RootResponse + 5, // 6: database_interface.Response.get:type_name -> database_interface.GetResponse + 7, // 7: database_interface.Response.prefetch:type_name -> database_interface.PrefetchResponse + 10, // 8: database_interface.Response.update:type_name -> database_interface.UpdateResponse + 12, // 9: database_interface.Response.close:type_name -> database_interface.CloseResponse + 8, // 10: database_interface.UpdateRequest.items:type_name -> database_interface.UpdateRequestItem + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_message_proto_init() } +func file_message_proto_init() { + if File_message_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RootRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RootResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrefetchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrefetchResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRequestItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_message_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Request_Root)(nil), + (*Request_Get)(nil), + (*Request_Prefetch)(nil), + (*Request_Update)(nil), + (*Request_Close)(nil), + } + file_message_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*Response_Root)(nil), + (*Response_Get)(nil), + (*Response_Prefetch)(nil), + (*Response_Update)(nil), + (*Response_Close)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_message_proto_rawDesc, + NumEnums: 0, + NumMessages: 13, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_message_proto_goTypes, + DependencyIndexes: file_message_proto_depIdxs, + MessageInfos: file_message_proto_msgTypes, + }.Build() + File_message_proto = out.File + file_message_proto_rawDesc = nil + file_message_proto_goTypes = nil + file_message_proto_depIdxs = nil +} From 1d9f5242b4a0e342e852f738aee27c6b8b12e61c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 10:59:26 -0800 Subject: [PATCH 256/307] fix --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index d6ac06c9fc..8a3e145e83 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -230,7 +230,7 @@ func TestPostProcess(t *testing.T) { tdb := triedb.NewDatabase(dbs.chain, tdbConfig) storage = legacy.New(tdb, lastRoot, lastHeight, true) } - require.Equal(t, lastRoot, storage.Root(), "Root mismatch") + require.Equal(t, lastRoot, common.BytesToHash(storage.Root()), "Root mismatch") storageRoot = lastRoot t.Logf("Storage backend initialized: %s", storageBackend) } From 7645f6e1f29435ded74934379d87589df335b402 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 11:02:57 -0800 Subject: [PATCH 257/307] try --- shim/legacy/legacy.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 4246a1ea38..f5fb127a2a 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -141,7 +141,11 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { if len(k) == 64 { continue } - accounts.MustUpdate(k, v) + if len(v) == 0 { + accounts.MustDelete(k) + } else { + accounts.MustUpdate(k, v) + } } // Verify account trie updates match the storage trie updates From 93f50a0b3d666c8415ebbd3d7676f96dc47d1270 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 11:29:31 -0800 Subject: [PATCH 258/307] fix comment --- triedb/database.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triedb/database.go b/triedb/database.go index 6af46ea97e..3bd4b43e41 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -46,8 +46,8 @@ type KVBackend interface { Prefetch(key []byte) ([]byte, error) // After this call, Root() should return the same hash as returned by this call. - // Note when length of a particular value, it means the corresponding key should - // be deleted. + // Note when length of a particular value is zero, it means the corresponding + // key should be deleted. // There may be duplicate keys in the batch provided, and the last one should // take effect. // Note after this call, the next call to Update must build on the returned root, From 403430d00c931687911eb414209a3ff762011dbf Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 11:31:57 -0800 Subject: [PATCH 259/307] clarify comment --- triedb/database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triedb/database.go b/triedb/database.go index 3bd4b43e41..08fdf24bf4 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -33,7 +33,7 @@ import ( type KVBackend interface { // Returns the current root hash of the trie. - // Empty trie must return common.Hash{}. + // Empty trie must return common.HashLength (32) worth of zero bytes. // Length of the returned slice must be common.HashLength. Root() []byte From 5c5eac8db7926f7a650df9132df7143a91d557cc Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 12:03:23 -0800 Subject: [PATCH 260/307] patch empty values for storage tries --- shim/legacy/legacy.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index f5fb127a2a..068edd3aa0 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -121,7 +121,11 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { } // Update the storage trie - tr.MustUpdate(k[32:], v) + if len(v) == 0 { + tr.MustDelete(k[32:]) + } else { + tr.MustUpdate(k[32:], v) + } } // Hash the storage tries From 269e70145a099c2f917c5be49049df90adb03cbe Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 13:32:13 -0800 Subject: [PATCH 261/307] try --- core/rawdb/accessors_snapshot.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 06a136ba89..b3ddc74204 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -27,6 +27,8 @@ package rawdb import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -111,6 +113,7 @@ func DeleteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash) { // ReadStorageSnapshot retrieves the snapshot entry of an storage trie leaf. func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) []byte { data, _ := db.Get(storageSnapshotKey(accountHash, storageHash)) + fmt.Printf("**** ReadStorageSnapshot: %x %x: %x\n", accountHash, storageHash, data) return data } From 80ec4e27f16c574d3274eb4a8c55c938414fa362 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 13:41:07 -0800 Subject: [PATCH 262/307] more verbose --- plugin/evm/post_processing_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 8a3e145e83..193986c51e 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -621,6 +621,9 @@ func processTape(t *testing.T, r io.Reader, tapeResult *tapeResult, cache func(k sum.accountReadHits++ } } + if tapeVerbose { + t.Logf("account read: %x -> %x", key, val) + } case typeStorage: key, val, err := readKV(r, 64) require.NoError(t, err) @@ -632,8 +635,14 @@ func processTape(t *testing.T, r io.Reader, tapeResult *tapeResult, cache func(k sum.storageReadHits++ } } + if tapeVerbose { + t.Logf("storage read: %x -> %x", key, val) + } case typeEndTx: txCount++ + if tapeVerbose { + t.Logf("end tx") + } } } return txCount From aee1e17682cc6befecac77eeae761abc2a889695 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 13:43:34 -0800 Subject: [PATCH 263/307] fix --- plugin/evm/post_processing_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 193986c51e..91926a34e2 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -286,7 +286,7 @@ func TestPostProcess(t *testing.T) { accountReads: make(map[string][]byte), storageReads: make(map[string][]byte), } - tapeTxs := processTape(t, r, tapeResult, cache.GetAndSet, &sum) + tapeTxs := processTape(t, r, tapeResult, cache.GetAndSet, &sum, tapeVerbose && blockNumber >= i) require.Equal(t, txs, tapeTxs) accountWrites, err := readUint16(r) @@ -598,7 +598,7 @@ type tapeResult struct { } // cache should return true if the value was found in the cache -func processTape(t *testing.T, r io.Reader, tapeResult *tapeResult, cache func(k string, v []byte) bool, sum *totals) uint16 { +func processTape(t *testing.T, r io.Reader, tapeResult *tapeResult, cache func(k string, v []byte) bool, sum *totals, tapeVerbose bool) uint16 { length, err := readUint32(r) require.NoError(t, err) From 0de3db251f84650078c64cf77832739a6666e363 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 13:54:18 -0800 Subject: [PATCH 264/307] add acc data --- core/rawdb/accessors_snapshot.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index b3ddc74204..34097007f0 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -93,6 +93,7 @@ func DeleteSnapshotBlockHash(db ethdb.KeyValueWriter) { // ReadAccountSnapshot retrieves the snapshot entry of an account trie leaf. func ReadAccountSnapshot(db ethdb.KeyValueReader, hash common.Hash) []byte { data, _ := db.Get(accountSnapshotKey(hash)) + fmt.Printf("**** ReadAccountSnapshot: %x: %x\n", hash, data) return data } From 99484c35cf165cadcec67bb0c43b1b42228a48a7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:30:45 -0800 Subject: [PATCH 265/307] try --- core/state/statedb.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index d6b5c9ab36..379fab524d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -46,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -728,6 +729,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if data == nil { return nil } + if s.snap != nil { + bytes, err := rlp.EncodeToBytes(data) + if err != nil { + s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) + return nil + } + + fmt.Printf("Warning: account %v not found in snapshot but found in trie as %x\n", addr, bytes) + } } // Insert into the live set obj := newObject(s, addr, data) From a91f731098dd056bab86975c4a4be65a936bb79e Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:44:55 -0800 Subject: [PATCH 266/307] try --- shim/geth_backend.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 08e433d617..15c2a39ffc 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -1,6 +1,8 @@ package shim import ( + "fmt" + "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb/database" @@ -40,7 +42,11 @@ func NewLegacyBackend( } func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } -func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } +func (b *LegacyBackend) Get(key []byte) ([]byte, error) { + val, err := b.tr.Get(key) + fmt.Printf("Get: %x %x\n", key, val) + return val, err +} func (b *LegacyBackend) Hash(batch Batch) common.Hash { if b.hashed { From 2a407a57731e9494f74f192bbc1de1e0abd5bc89 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:48:55 -0800 Subject: [PATCH 267/307] try --- plugin/evm/post_processing_test.go | 1 - shim/geth_backend.go | 8 +------- shim/trie.go | 7 +++++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 91926a34e2..cecf9e0b7c 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -488,7 +488,6 @@ func TestPostProcess(t *testing.T) { if storageBackend == "legacy" { require.Equal(t, storageRoot, block.Root(), "Root mismatch") } - } updateMetadata(t, dbs.metadata, blockHash, storageRoot, blockNumber) diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 15c2a39ffc..08e433d617 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -1,8 +1,6 @@ package shim import ( - "fmt" - "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb/database" @@ -42,11 +40,7 @@ func NewLegacyBackend( } func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } -func (b *LegacyBackend) Get(key []byte) ([]byte, error) { - val, err := b.tr.Get(key) - fmt.Printf("Get: %x %x\n", key, val) - return val, err -} +func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } func (b *LegacyBackend) Hash(batch Batch) common.Hash { if b.hashed { diff --git a/shim/trie.go b/shim/trie.go index 8a2377dc8e..6166d16124 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -2,6 +2,7 @@ package shim import ( "bytes" + "fmt" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -77,7 +78,9 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) func (t *Trie) Get(key []byte) ([]byte, error) { key = t.getKey(key) - return t.backend.Get(key) + val, err := t.backend.Get(key) + fmt.Printf("Get: %x -> %x\n", key, val) + return val, err } func (t *Trie) Prefetch(key []byte) ([]byte, error) { @@ -93,7 +96,7 @@ func (t *Trie) Copy() *Trie { addrHash: legacy.addrHash, writer: legacy.writer, }, - prefix: t.prefix, + prefix: bytes.Clone(t.prefix), parent: t.parent, origin: t.origin, changes: append(Batch(nil), t.changes...), From 35fdc090e517bba304c4b1290ce29d178a8b50c1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:52:15 -0800 Subject: [PATCH 268/307] try --- shim/trie.go | 1 + 1 file changed, 1 insertion(+) diff --git a/shim/trie.go b/shim/trie.go index 6166d16124..02f653531a 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -102,6 +102,7 @@ func (t *Trie) Copy() *Trie { changes: append(Batch(nil), t.changes...), } } + panic("unexpected backend") // fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) return t } From eb61f696121f1ac2e9bd0132ae67468aea4a8159 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:52:48 -0800 Subject: [PATCH 269/307] undo --- shim/trie.go | 1 - 1 file changed, 1 deletion(-) diff --git a/shim/trie.go b/shim/trie.go index 02f653531a..6166d16124 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -102,7 +102,6 @@ func (t *Trie) Copy() *Trie { changes: append(Batch(nil), t.changes...), } } - panic("unexpected backend") // fmt.Printf("Copy requested (%d changes): %p: %x\n", len(t.changes), t, t.prefix) return t } From 21396aaa75cb7365a32d7f837cd4da308a3d37eb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:55:24 -0800 Subject: [PATCH 270/307] try --- shim/geth_backend.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 08e433d617..00ab0cd087 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -1,6 +1,8 @@ package shim import ( + "fmt" + "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb/database" @@ -40,7 +42,15 @@ func NewLegacyBackend( } func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } -func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } +func (b *LegacyBackend) Get(key []byte) ([]byte, error) { + val, err := b.tr.Get(key) + if b.addrHash != (common.Hash{}) { + fmt.Printf("Get: %x%x %x\n", b.addrHash, key, val) + } else { + fmt.Printf("Get: %x %x\n", key, val) + } + return val, err +} func (b *LegacyBackend) Hash(batch Batch) common.Hash { if b.hashed { From 2610e0fe0208b8eea38122da38d7bbca67550060 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 14:58:34 -0800 Subject: [PATCH 271/307] undo --- shim/trie.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shim/trie.go b/shim/trie.go index 6166d16124..a0e0c9d207 100644 --- a/shim/trie.go +++ b/shim/trie.go @@ -2,7 +2,6 @@ package shim import ( "bytes" - "fmt" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" @@ -78,9 +77,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) func (t *Trie) Get(key []byte) ([]byte, error) { key = t.getKey(key) - val, err := t.backend.Get(key) - fmt.Printf("Get: %x -> %x\n", key, val) - return val, err + return t.backend.Get(key) } func (t *Trie) Prefetch(key []byte) ([]byte, error) { From 232e74f868e7fb62a822fafdbdc0e91bb18f7a4a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:06:19 -0800 Subject: [PATCH 272/307] try --- shim/geth_backend.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 00ab0cd087..3da4d9daeb 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -1,6 +1,7 @@ package shim import ( + "bytes" "fmt" "github.com/ava-labs/coreth/trie" @@ -41,11 +42,16 @@ func NewLegacyBackend( return &LegacyBackend{tr: tr, addrHash: addrHash, writer: writer}, nil } +var target = common.Hex2Bytes("171ab08901be24769dbebedbdf7e0245486fbc64ab975cd431a39533032d5415") + func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } func (b *LegacyBackend) Get(key []byte) ([]byte, error) { val, err := b.tr.Get(key) if b.addrHash != (common.Hash{}) { fmt.Printf("Get: %x%x %x\n", b.addrHash, key, val) + if bytes.Equal(key, target) { + panic("found") + } } else { fmt.Printf("Get: %x %x\n", key, val) } From 58a793717941d01f7b7908f33c6b402f0fac6bfa Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:14:12 -0800 Subject: [PATCH 273/307] try --- core/state/state_object.go | 6 ++++++ shim/geth_backend.go | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 8f0493ed3d..0a06971832 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -227,6 +227,10 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { s.db.setError(err) return common.Hash{} } + if crypto.Keccak256Hash(key.Bytes()) == common.Hash(target) { + fmt.Println("GetState: ", s.address, key, value) + panic("found") + } val, err := tr.GetStorage(s.address, key.Bytes()) if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) @@ -241,6 +245,8 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { return value } +var target = common.Hex2Bytes("171ab08901be24769dbebedbdf7e0245486fbc64ab975cd431a39533032d5415") + // SetState updates a value in account storage. func (s *stateObject) SetState(key, value common.Hash) { // If the new value is the same as old, don't set diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 3da4d9daeb..00ab0cd087 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -1,7 +1,6 @@ package shim import ( - "bytes" "fmt" "github.com/ava-labs/coreth/trie" @@ -42,16 +41,11 @@ func NewLegacyBackend( return &LegacyBackend{tr: tr, addrHash: addrHash, writer: writer}, nil } -var target = common.Hex2Bytes("171ab08901be24769dbebedbdf7e0245486fbc64ab975cd431a39533032d5415") - func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } func (b *LegacyBackend) Get(key []byte) ([]byte, error) { val, err := b.tr.Get(key) if b.addrHash != (common.Hash{}) { fmt.Printf("Get: %x%x %x\n", b.addrHash, key, val) - if bytes.Equal(key, target) { - panic("found") - } } else { fmt.Printf("Get: %x %x\n", key, val) } From 23b591625556836008a50d6804bfa92baa649512 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:15:19 -0800 Subject: [PATCH 274/307] get val --- core/state/state_object.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 0a06971832..3bcde897e1 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -227,11 +227,11 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { s.db.setError(err) return common.Hash{} } + val, err := tr.GetStorage(s.address, key.Bytes()) if crypto.Keccak256Hash(key.Bytes()) == common.Hash(target) { fmt.Println("GetState: ", s.address, key, value) panic("found") } - val, err := tr.GetStorage(s.address, key.Bytes()) if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } From 1649262b4577aa571d132be788ad24618a59871c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:20:38 -0800 Subject: [PATCH 275/307] try --- plugin/evm/reprocess_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 35be373573..730b22af00 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -226,6 +226,27 @@ func TestExportCode(t *testing.T) { } } +func TestExportHeaders(t *testing.T) { + sourceDb := openSourceDB(t) + defer sourceDb.Close() + + dbs := openDBs(t) + defer dbs.Close() + + db := dbs.chain + + for i := startBlock; i <= endBlock; i++ { + hash := rawdb.ReadCanonicalHash(sourceDb, i) + header := rawdb.ReadHeader(sourceDb, hash, i) + if header == nil { + t.Fatalf("Header %d not found", i) + } + rawdb.WriteHeader(db, header) + } + + t.Logf("Exported %d headers", endBlock-startBlock+1) +} + func TestQueryBlock(t *testing.T) { sourceDb := openSourceDB(t) defer sourceDb.Close() From 60f791334dace775589f31755de77eeaafd614de Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:23:18 -0800 Subject: [PATCH 276/307] add log --- plugin/evm/reprocess_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 730b22af00..30f2f80d84 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -242,6 +242,10 @@ func TestExportHeaders(t *testing.T) { t.Fatalf("Header %d not found", i) } rawdb.WriteHeader(db, header) + + if i%uint64(logEach) == 0 { + t.Logf("Exported header %d", i) + } } t.Logf("Exported %d headers", endBlock-startBlock+1) From 8a1bb224fdc28e4bf2f41fe8770efc80e1b58419 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:26:11 -0800 Subject: [PATCH 277/307] fix --- core/state/state_object.go | 6 ------ shim/geth_backend.go | 12 +----------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 3bcde897e1..8f0493ed3d 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -228,10 +228,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { return common.Hash{} } val, err := tr.GetStorage(s.address, key.Bytes()) - if crypto.Keccak256Hash(key.Bytes()) == common.Hash(target) { - fmt.Println("GetState: ", s.address, key, value) - panic("found") - } if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } @@ -245,8 +241,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { return value } -var target = common.Hex2Bytes("171ab08901be24769dbebedbdf7e0245486fbc64ab975cd431a39533032d5415") - // SetState updates a value in account storage. func (s *stateObject) SetState(key, value common.Hash) { // If the new value is the same as old, don't set diff --git a/shim/geth_backend.go b/shim/geth_backend.go index 00ab0cd087..08e433d617 100644 --- a/shim/geth_backend.go +++ b/shim/geth_backend.go @@ -1,8 +1,6 @@ package shim import ( - "fmt" - "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb/database" @@ -42,15 +40,7 @@ func NewLegacyBackend( } func (b *LegacyBackend) Prefetch(key []byte) ([]byte, error) { return b.tr.Get(key) } -func (b *LegacyBackend) Get(key []byte) ([]byte, error) { - val, err := b.tr.Get(key) - if b.addrHash != (common.Hash{}) { - fmt.Printf("Get: %x%x %x\n", b.addrHash, key, val) - } else { - fmt.Printf("Get: %x %x\n", key, val) - } - return val, err -} +func (b *LegacyBackend) Get(key []byte) ([]byte, error) { return b.tr.Get(key) } func (b *LegacyBackend) Hash(batch Batch) common.Hash { if b.hashed { From 2e5952a4b08f646f2d2a97678c8bff284309d2c7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 10 Jan 2025 15:41:06 -0800 Subject: [PATCH 278/307] undo logging --- core/rawdb/accessors_snapshot.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 34097007f0..06a136ba89 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -27,8 +27,6 @@ package rawdb import ( - "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -93,7 +91,6 @@ func DeleteSnapshotBlockHash(db ethdb.KeyValueWriter) { // ReadAccountSnapshot retrieves the snapshot entry of an account trie leaf. func ReadAccountSnapshot(db ethdb.KeyValueReader, hash common.Hash) []byte { data, _ := db.Get(accountSnapshotKey(hash)) - fmt.Printf("**** ReadAccountSnapshot: %x: %x\n", hash, data) return data } @@ -114,7 +111,6 @@ func DeleteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash) { // ReadStorageSnapshot retrieves the snapshot entry of an storage trie leaf. func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) []byte { data, _ := db.Get(storageSnapshotKey(accountHash, storageHash)) - fmt.Printf("**** ReadStorageSnapshot: %x %x: %x\n", accountHash, storageHash, data) return data } From 0c548b4290440996b72e5174ac2564d38103dc3a Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 13 Jan 2025 08:10:52 -0800 Subject: [PATCH 279/307] apply logEach to tapeDir mode as well --- plugin/evm/reprocess_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 30f2f80d84..c7b88aaf8d 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -566,12 +566,12 @@ func reprocess( err = bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) - if tapeRecorder != nil { - tapeRecorder.Summary(block, uint16(len(atomicTxs))) - tapeRecorder.WriteToDisk(block, uint16(len(atomicTxs))) - tapeRecorder.Reset() - } else { - if i%uint64(logEach) == 0 { + if i%uint64(logEach) == 0 { + if tapeRecorder != nil { + tapeRecorder.Summary(block, uint16(len(atomicTxs))) + tapeRecorder.WriteToDisk(block, uint16(len(atomicTxs))) + tapeRecorder.Reset() + } else { t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) } } From 63f92937e7359bc38ab73d30032fc597a515efe2 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 13 Jan 2025 08:11:13 -0800 Subject: [PATCH 280/307] oops --- plugin/evm/reprocess_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index c7b88aaf8d..ce7edce89c 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -566,12 +566,14 @@ func reprocess( err = bc.InsertBlockManualWithParent(block, parent, true) require.NoError(t, err) - if i%uint64(logEach) == 0 { - if tapeRecorder != nil { + if tapeRecorder != nil { + if i%uint64(logEach) == 0 { tapeRecorder.Summary(block, uint16(len(atomicTxs))) - tapeRecorder.WriteToDisk(block, uint16(len(atomicTxs))) - tapeRecorder.Reset() - } else { + } + tapeRecorder.WriteToDisk(block, uint16(len(atomicTxs))) + tapeRecorder.Reset() + } else { + if i%uint64(logEach) == 0 { t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) } } From 35eec6873a4a2c16c2078b0610d01c33ac6290cc Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 13 Jan 2025 11:38:04 -0800 Subject: [PATCH 281/307] trackDeletedTries --- plugin/evm/post_processing_test.go | 10 +++++++- plugin/evm/reprocess_test.go | 2 ++ shim/legacy/legacy.go | 40 ++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index cecf9e0b7c..af21c802c4 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -9,6 +9,7 @@ import ( "github.com/VictoriaMetrics/fastcache" "github.com/Yiling-J/theine-go" + "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state/snapshot" @@ -228,7 +229,14 @@ func TestPostProcess(t *testing.T) { cacheConfig := getCacheConfig(t, storageBackend, storage) tdbConfig := cacheConfig.TrieDBConfig() tdb := triedb.NewDatabase(dbs.chain, tdbConfig) - storage = legacy.New(tdb, lastRoot, lastHeight, true) + legacyStore := legacy.New(tdb, lastRoot, lastHeight, true) + // install selfdestruct re-use detection if requested + if trackDeletedTries { + store := prefixdb.New([]byte("trackDeletedTries"), dbs.metadata) + legacyStore.TrackDeletedTries(rawdb.NewDatabase(Database{store})) + t.Logf("Enabled trackDeletedTries") + } + storage = legacyStore } require.Equal(t, lastRoot, common.BytesToHash(storage.Root()), "Root mismatch") storageRoot = lastRoot diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index ce7edce89c..a338bc14cd 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -58,6 +58,7 @@ var ( commitEachBlocks = 1 commitEachTxs = 0 forceStartWithMismatch = false + trackDeletedTries = false // merkledb options merkleDBBranchFactor = 16 @@ -96,6 +97,7 @@ func TestMain(m *testing.M) { flag.IntVar(&commitEachBlocks, "commitEachBlocks", commitEachBlocks, "commit each N blocks") flag.IntVar(&commitEachTxs, "commitEachTxs", commitEachTxs, "commit each N transactions") flag.BoolVar(&forceStartWithMismatch, "forceStartWithMismatch", forceStartWithMismatch, "force start with mismatch") + flag.BoolVar(&trackDeletedTries, "trackDeletedTries", trackDeletedTries, "track deleted tries (detect re-use of SELFDESTRUCTed accounts)") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 068edd3aa0..be606ffce2 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -8,16 +8,18 @@ import ( "github.com/ava-labs/coreth/trie/trienode" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" ) var _ triedb.KVBackend = &Legacy{} type Legacy struct { - triedb *triedb.Database - root common.Hash - count uint64 - dereference bool + triedb *triedb.Database + root common.Hash + count uint64 + dereference bool + trackDeletedTries ethdb.KeyValueStore } func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bool) *Legacy { @@ -29,6 +31,10 @@ func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bo } } +func (l *Legacy) TrackDeletedTries(db ethdb.KeyValueStore) { + l.trackDeletedTries = db +} + func getAccountRoot(tr *trie.Trie, accHash common.Hash) (common.Hash, error) { root := types.EmptyRootHash accBytes, err := tr.Get(accHash[:]) @@ -84,6 +90,13 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { // Remove the account from the account trie so we don't try to delete it again accounts.MustDelete(k[:32]) + // Track that this account is deleted + if l.trackDeletedTries != nil { + if err := l.trackDeletedTries.Put(accHash[:], []byte{1}); err != nil { + return nil, fmt.Errorf("failed to track deleted trie %x: %w", accHash, err) + } + } + if pending, ok := tries[accHash]; ok { // If there are pending updates for this account, we should not apply them // but log them for checking @@ -96,10 +109,12 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { fmt.Printf("::: dropped trie with %d pending updates for %x\n", keys, accHash) // Also any pending updates should not be apply // Further updates shold apply to an empty trie - tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) - if err != nil { - return nil, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) - } + delete(tries, accHash) + // Previous code: + // tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) + // if err != nil { + // return nil, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) + // } } } @@ -109,6 +124,15 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { tr, ok := tries[accHash] if !ok { + if l.trackDeletedTries != nil { + found, err := l.trackDeletedTries.Has(accHash[:]) + if err != nil { + return nil, fmt.Errorf("failed to check if account %x is deleted: %w", accHash, err) + } + if found { + return nil, fmt.Errorf("attempt to update deleted account %x", accHash) + } + } root, err := getAccountRoot(accounts, accHash) if err != nil { return nil, err From 84ea934c845a2bd94cbf371ab5bb83d3a9a33571 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Mon, 13 Jan 2025 12:26:48 -0800 Subject: [PATCH 282/307] only track actual deletes --- shim/legacy/legacy.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index be606ffce2..31a7c508bc 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -1,6 +1,7 @@ package legacy import ( + "encoding/binary" "fmt" "github.com/ava-labs/coreth/core/types" @@ -86,16 +87,16 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { fmt.Printf("::: set merged: %d updates, %d deletes (%d leafs deleted)\n", updates, deletes, leafs) nodes.Merge(set) } - } - // Remove the account from the account trie so we don't try to delete it again - accounts.MustDelete(k[:32]) - // Track that this account is deleted - if l.trackDeletedTries != nil { - if err := l.trackDeletedTries.Put(accHash[:], []byte{1}); err != nil { - return nil, fmt.Errorf("failed to track deleted trie %x: %w", accHash, err) + // Track that this account is deleted + if l.trackDeletedTries != nil { + if err := l.trackDeletedTries.Put(accHash[:], binary.BigEndian.AppendUint64(nil, l.count)); err != nil { + return nil, fmt.Errorf("failed to track deleted trie %x: %w", accHash, err) + } } } + // Remove the account from the account trie so we don't try to delete it again + accounts.MustDelete(k[:32]) if pending, ok := tries[accHash]; ok { // If there are pending updates for this account, we should not apply them @@ -115,6 +116,13 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { // if err != nil { // return nil, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) // } + + // Track that this account is deleted + if l.trackDeletedTries != nil { + if err := l.trackDeletedTries.Put(accHash[:], binary.BigEndian.AppendUint64(nil, l.count)); err != nil { + return nil, fmt.Errorf("failed to track deleted trie %x: %w", accHash, err) + } + } } } From 6a9d0c7c22fa604eed6650e775cc2517fe516b92 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 10:15:34 -0800 Subject: [PATCH 283/307] log deleted tries --- plugin/evm/post_processing_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index af21c802c4..f9b9012b67 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -235,6 +235,13 @@ func TestPostProcess(t *testing.T) { store := prefixdb.New([]byte("trackDeletedTries"), dbs.metadata) legacyStore.TrackDeletedTries(rawdb.NewDatabase(Database{store})) t.Logf("Enabled trackDeletedTries") + + it := store.NewIterator() + for it.Next() { + t.Logf("starting with trackDeletedTries: %x", it.Key()) + } + require.NoError(t, it.Error()) + it.Release() } storage = legacyStore } From 023897ed49efcc3940ad9a908e75265bc1685474 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 10:21:53 -0800 Subject: [PATCH 284/307] add more logging --- shim/legacy/legacy.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 31a7c508bc..4798060178 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -107,7 +107,7 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { for it.Next() { keys++ } - fmt.Printf("::: dropped trie with %d pending updates for %x\n", keys, accHash) + fmt.Printf("::: dropped trie with %d pending updates for %x at idx %d\n", keys, accHash, i) // Also any pending updates should not be apply // Further updates shold apply to an empty trie delete(tries, accHash) @@ -138,7 +138,12 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { return nil, fmt.Errorf("failed to check if account %x is deleted: %w", accHash, err) } if found { - return nil, fmt.Errorf("attempt to update deleted account %x", accHash) + got, err := l.trackDeletedTries.Get(accHash[:]) + if err != nil { + return nil, fmt.Errorf("failed to get deleted trie %x: %w", accHash, err) + } + fmt.Println("::: found deleted trie", accHash, binary.BigEndian.Uint64(got), l.count, i) + return nil, fmt.Errorf("account %x is deleted", accHash) } } root, err := getAccountRoot(accounts, accHash) From 0746e7f3735b398a046fe02d20c6cc412f4ca8d8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 10:44:35 -0800 Subject: [PATCH 285/307] fix --- plugin/evm/post_processing_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f9b9012b67..c75b1b24d8 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -467,8 +467,9 @@ func TestPostProcess(t *testing.T) { } } - evictedKs, evictedVs = evictedKs[:0], evictedVs[:0] } + evictedKs, evictedVs = evictedKs[:0], evictedVs[:0] + if sourceDb != nil { // update block and metadata from source db hash := rawdb.ReadCanonicalHash(sourceDb, blockNumber) From bb1fcb9e256d20ff9c7c61bb9bd689bffc75a44c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 10:45:29 -0800 Subject: [PATCH 286/307] wspace --- plugin/evm/post_processing_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index c75b1b24d8..2103cb88cc 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -466,7 +466,6 @@ func TestPostProcess(t *testing.T) { } } } - } evictedKs, evictedVs = evictedKs[:0], evictedVs[:0] From b095d898c0664bd7b5a1208aad71b3ceb38960a9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 11:32:29 -0800 Subject: [PATCH 287/307] add prefix delete in post_processing --- plugin/evm/post_processing_test.go | 78 +++++++++++++------------ shim/legacy/legacy.go | 92 +++++++++++++++--------------- shim/legacy/snapshot.go | 82 ++++++++++++++++++++++++++ shim/merkledb/merkledb.go | 4 ++ shim/nomt/nomt.go | 4 ++ triedb/database.go | 4 ++ 6 files changed, 181 insertions(+), 83 deletions(-) create mode 100644 shim/legacy/snapshot.go diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 2103cb88cc..f483a23587 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -13,12 +13,10 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state/snapshot" - "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/shim/legacy" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/rlp" lru "github.com/hashicorp/golang-lru/v2" "github.com/maypok86/otter" "github.com/stretchr/testify/require" @@ -414,6 +412,15 @@ func TestPostProcess(t *testing.T) { t.Logf("storing: %x -> %x", k, evictedVs[i]) } } + + var accsDeleted map[string]int + evictedKs, evictedVs, accsDeleted = processAccountDeletes(t, evictedKs, evictedVs) + for k := range accsDeleted { + deleted, err := storage.PrefixDelete([]byte(k)) + require.NoError(t, err) + t.Logf("Deleted %d keys with prefix %x from storage", deleted, k) + } + now := time.Now() // Get state commitment from storage backend storageRootBytes, err := storage.Update(evictedKs, evictedVs) @@ -431,42 +438,18 @@ func TestPostProcess(t *testing.T) { sum.storageUpdateCount++ if writeSnapshot { - for i, k := range evictedKs { - v := evictedVs[i] - if len(k) == 32 { - if len(v) == 0 { - rawdb.DeleteAccountSnapshot(dbs.chain, common.BytesToHash(k)) - it := dbs.chain.NewIterator(append(rawdb.SnapshotStoragePrefix, k...), nil) - keysDeleted := 0 - for it.Next() { - k := it.Key()[len(rawdb.SnapshotStoragePrefix):] - rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) - keysDeleted++ - } - if err := it.Error(); err != nil { - t.Fatalf("Failed to iterate over snapshot account: %v", err) - } - it.Release() - if keysDeleted > 0 { - t.Logf("Deleted %d storage keys for account %x", keysDeleted, k) - } - } else { - var acc types.StateAccount - if err := rlp.DecodeBytes(v, &acc); err != nil { - t.Fatalf("Failed to decode account: %v", err) - } - data := types.SlimAccountRLP(acc) - rawdb.WriteAccountSnapshot(dbs.chain, common.BytesToHash(k), data) - } - } else { - if len(v) > 0 { - rawdb.WriteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:]), v) - } else { - rawdb.DeleteStorageSnapshot(dbs.chain, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) - } - } + snapBackend := legacy.NewSnapshot(dbs.chain) + for acc := range accsDeleted { + deleted, err := snapBackend.PrefixDelete([]byte(acc)) + require.NoError(t, err) + t.Logf("Deleted %d keys with prefix %x from snapshot", deleted, acc) } + + _, err = snapBackend.Update(evictedKs, evictedVs) + require.NoError(t, err) } + + // Reset evicted batch evictedKs, evictedVs = evictedKs[:0], evictedVs[:0] if sourceDb != nil { @@ -607,6 +590,29 @@ func TestPostProcess(t *testing.T) { } } +func processAccountDeletes(t *testing.T, ks, vs [][]byte) ([][]byte, [][]byte, map[string]int) { + accsDeleted := make(map[string]int) + for i, k := range ks { + if len(k) == 32 && len(vs[i]) == 0 { + accsDeleted[string(k)] = i // all updates with prefix k that occur before i should be omitted from the return value + } + } + outIdx := 0 + for i, k := range ks { + prefix := k[:32] + if idx, found := accsDeleted[string(prefix)]; found && i < idx { + continue + } + ks[outIdx] = k + vs[outIdx] = vs[i] + outIdx++ + } + if outIdx < len(ks) { + t.Logf("Removed %d updates from pending batch", len(ks)-outIdx) + } + return ks[:outIdx], vs[:outIdx], accsDeleted +} + type tapeResult struct { accountReads, storageReads map[string][]byte } diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 4798060178..61746961e4 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -70,59 +70,15 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { accHash := common.BytesToHash(k[:32]) if len(k) == 32 { if len(v) == 0 { - // this trie is DELETED. First we will remove it from storage: - // if it was updated before, we should have a trie for it prevRoot, err := getAccountRoot(accounts, accHash) if err != nil { return nil, fmt.Errorf("failed to get account root %x: %w", accHash, err) } if prevRoot != types.EmptyRootHash { - fmt.Printf(":: slow delete storage for %x\n", accHash) - _, leafs, _, set, err := slowDeleteStorage(l.triedb, l.root, accHash, prevRoot) - if err != nil { - return nil, err - } - if set != nil { - updates, deletes := set.Size() - fmt.Printf("::: set merged: %d updates, %d deletes (%d leafs deleted)\n", updates, deletes, leafs) - nodes.Merge(set) - } - - // Track that this account is deleted - if l.trackDeletedTries != nil { - if err := l.trackDeletedTries.Put(accHash[:], binary.BigEndian.AppendUint64(nil, l.count)); err != nil { - return nil, fmt.Errorf("failed to track deleted trie %x: %w", accHash, err) - } - } + return nil, fmt.Errorf("account %x is deleted but has non-empty storage trie", accHash) } - // Remove the account from the account trie so we don't try to delete it again - accounts.MustDelete(k[:32]) - - if pending, ok := tries[accHash]; ok { - // If there are pending updates for this account, we should not apply them - // but log them for checking - nodeIt := pending.MustNodeIterator(nil) - it := trie.NewIterator(nodeIt) - keys := 0 - for it.Next() { - keys++ - } - fmt.Printf("::: dropped trie with %d pending updates for %x at idx %d\n", keys, accHash, i) - // Also any pending updates should not be apply - // Further updates shold apply to an empty trie - delete(tries, accHash) - // Previous code: - // tries[accHash], err = trie.New(trie.StorageTrieID(l.root, accHash, types.EmptyRootHash), l.triedb) - // if err != nil { - // return nil, fmt.Errorf("failed to create storage trie %x: %w", accHash, err) - // } - - // Track that this account is deleted - if l.trackDeletedTries != nil { - if err := l.trackDeletedTries.Put(accHash[:], binary.BigEndian.AppendUint64(nil, l.count)); err != nil { - return nil, fmt.Errorf("failed to track deleted trie %x: %w", accHash, err) - } - } + if _, ok := tries[accHash]; ok { + return nil, fmt.Errorf("account %x is deleted but has pending storage trie", accHash) } } @@ -274,3 +230,45 @@ func slowDeleteStorage( } return false, leafs, slots, nodes, nil } + +func (l *Legacy) PrefixDelete(prefix []byte) (int, error) { + if l.trackDeletedTries != nil { + if err := l.trackDeletedTries.Put(prefix, binary.BigEndian.AppendUint64(nil, l.count)); err != nil { + return 0, fmt.Errorf("failed to track deleted trie %x: %w", prefix, err) + } + } + + accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) + if err != nil { + return 0, err + } + origin, err := getAccountRoot(accounts, common.BytesToHash(prefix)) + if err != nil { + return 0, err + } + if origin == types.EmptyRootHash { + return 0, nil + } + nodes := trienode.NewMergedNodeSet() + _, leafs, _, set, err := slowDeleteStorage(l.triedb, l.root, common.BytesToHash(prefix), origin) + if err != nil { + return 0, err + } + if set != nil { + nodes.Merge(set) + } + accounts.MustDelete(prefix) + next, set, err := accounts.Commit(true) + if err != nil { + return 0, err + } + if set != nil { + nodes.Merge(set) + } + if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { + return 0, err + } + l.root = next + l.count++ + return leafs, nil +} diff --git a/shim/legacy/snapshot.go b/shim/legacy/snapshot.go new file mode 100644 index 0000000000..5ef09fd5a3 --- /dev/null +++ b/shim/legacy/snapshot.go @@ -0,0 +1,82 @@ +package legacy + +import ( + "github.com/ava-labs/coreth/core/rawdb" + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/triedb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" +) + +var _ triedb.KVBackend = &Snapshot{} + +type Snapshot struct { + db ethdb.Database +} + +func NewSnapshot(db ethdb.Database) *Snapshot { + return &Snapshot{db: db} +} + +func (s *Snapshot) Get(key []byte) ([]byte, error) { + acc := common.BytesToHash(key[:32]) + var val []byte + if len(key) == 32 { + val = rawdb.ReadAccountSnapshot(s.db, acc) + } else { + val = rawdb.ReadStorageSnapshot(s.db, acc, common.BytesToHash(key[32:])) + } + return val, nil +} + +func (s *Snapshot) Prefetch(key []byte) ([]byte, error) { + return nil, nil +} + +func (s *Snapshot) Update(ks, vs [][]byte) ([]byte, error) { + for i, k := range ks { + acc := common.BytesToHash(k[:32]) + if len(k) == 32 { + if len(vs[i]) == 0 { + rawdb.DeleteAccountSnapshot(s.db, acc) + } else { + var account types.StateAccount + if err := rlp.DecodeBytes(vs[i], &account); err != nil { + return nil, err + } + data := types.SlimAccountRLP(account) + rawdb.WriteAccountSnapshot(s.db, acc, data) + } + } else { + if len(vs[i]) == 0 { + rawdb.DeleteStorageSnapshot(s.db, acc, common.BytesToHash(k[32:])) + } else { + rawdb.WriteStorageSnapshot(s.db, acc, common.BytesToHash(k[32:]), vs[i]) + } + } + } + return nil, nil +} + +func (s *Snapshot) Commit(root []byte) error { return nil } +func (s *Snapshot) Close() error { return nil } +func (s *Snapshot) Root() []byte { return nil } + +func (s *Snapshot) PrefixDelete(k []byte) (int, error) { + rawdb.DeleteAccountSnapshot(s.db, common.BytesToHash(k)) + + it := s.db.NewIterator(append(rawdb.SnapshotStoragePrefix, k...), nil) + defer it.Release() + + keysDeleted := 0 + for it.Next() { + k := it.Key()[len(rawdb.SnapshotStoragePrefix):] + rawdb.DeleteStorageSnapshot(s.db, common.BytesToHash(k[:32]), common.BytesToHash(k[32:])) + keysDeleted++ + } + if err := it.Error(); err != nil { + return 0, err + } + return keysDeleted, nil +} diff --git a/shim/merkledb/merkledb.go b/shim/merkledb/merkledb.go index 4a20bec59b..f1d8b6fe31 100644 --- a/shim/merkledb/merkledb.go +++ b/shim/merkledb/merkledb.go @@ -138,3 +138,7 @@ func (m *MerkleDB) Close() error { return m.db.Close() } + +func (m *MerkleDB) PrefixDelete(prefix []byte) (int, error) { + return 0, nil +} diff --git a/shim/nomt/nomt.go b/shim/nomt/nomt.go index b6e4884b9e..2820208b01 100644 --- a/shim/nomt/nomt.go +++ b/shim/nomt/nomt.go @@ -140,3 +140,7 @@ func (n *Nomt) Close() error { return n.conn.Close() } + +func (n *Nomt) PrefixDelete(prefix []byte) (int, error) { + return 0, nil +} diff --git a/triedb/database.go b/triedb/database.go index 08fdf24bf4..a25c12c20d 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -63,6 +63,10 @@ type KVBackend interface { // Close closes the backend and releases all held resources. Close() error + + // PrefixDelete should delete all keys with the given prefix, and return the + // number of keys deleted. + PrefixDelete(prefix []byte) (int, error) } type KVWriter interface { From e6ae493329714d39b197d234bd5080f7d501ec52 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 11:34:22 -0800 Subject: [PATCH 288/307] log nit --- plugin/evm/post_processing_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index f483a23587..a7effe220b 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -418,7 +418,9 @@ func TestPostProcess(t *testing.T) { for k := range accsDeleted { deleted, err := storage.PrefixDelete([]byte(k)) require.NoError(t, err) - t.Logf("Deleted %d keys with prefix %x from storage", deleted, k) + if deleted > 0 { + t.Logf("Deleted %d keys with prefix %x from storage", deleted, k) + } } now := time.Now() @@ -442,7 +444,9 @@ func TestPostProcess(t *testing.T) { for acc := range accsDeleted { deleted, err := snapBackend.PrefixDelete([]byte(acc)) require.NoError(t, err) - t.Logf("Deleted %d keys with prefix %x from snapshot", deleted, acc) + if deleted > 0 { + t.Logf("Deleted %d keys with prefix %x from snapshot", deleted, acc) + } } _, err = snapBackend.Update(evictedKs, evictedVs) From e3f6ac15f7a006deaeb63073cbcdb30734064290 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 11:42:50 -0800 Subject: [PATCH 289/307] only need to track non-empty --- shim/legacy/legacy.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 61746961e4..3436bb0af4 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -232,12 +232,6 @@ func slowDeleteStorage( } func (l *Legacy) PrefixDelete(prefix []byte) (int, error) { - if l.trackDeletedTries != nil { - if err := l.trackDeletedTries.Put(prefix, binary.BigEndian.AppendUint64(nil, l.count)); err != nil { - return 0, fmt.Errorf("failed to track deleted trie %x: %w", prefix, err) - } - } - accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) if err != nil { return 0, err @@ -249,6 +243,11 @@ func (l *Legacy) PrefixDelete(prefix []byte) (int, error) { if origin == types.EmptyRootHash { return 0, nil } + if l.trackDeletedTries != nil { + if err := l.trackDeletedTries.Put(prefix, binary.BigEndian.AppendUint64(nil, l.count)); err != nil { + return 0, fmt.Errorf("failed to track deleted trie %x: %w", prefix, err) + } + } nodes := trienode.NewMergedNodeSet() _, leafs, _, set, err := slowDeleteStorage(l.triedb, l.root, common.BytesToHash(prefix), origin) if err != nil { From bb9b6bac7ce2b28803ab96ad0cccacbf552dbbd8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 11:51:42 -0800 Subject: [PATCH 290/307] use prefixdelete in opcode selfdestruct --- core/state/statedb.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index 379fab524d..4a6d328c25 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1268,6 +1268,16 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if prev.Root == types.EmptyRootHash { continue } + tdbConfig := s.db.TrieDB().Config() + if tdbConfig.KeyValueDB != nil && tdbConfig.KeyValueDB.KVBackend != nil { + deleted, err := tdbConfig.KeyValueDB.KVBackend.PrefixDelete(addrHash[:]) + if err != nil { + return nil, fmt.Errorf("failed to delete storage from kv backend, err: %w", err) + } + if deleted > 0 { + log.Info("Deleted storage from kv backend", "addrHash", addrHash, "deleted", deleted) + } + } // Remove storage slots belong to the account. aborted, slots, set, err := s.deleteStorage(addr, addrHash, prev.Root) if err != nil { From 543a7dff5247c81157d9969a207e57d46fabd774 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 14 Jan 2025 11:55:05 -0800 Subject: [PATCH 291/307] Revert "only need to track non-empty" This reverts commit e3f6ac15f7a006deaeb63073cbcdb30734064290. --- shim/legacy/legacy.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 3436bb0af4..61746961e4 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -232,6 +232,12 @@ func slowDeleteStorage( } func (l *Legacy) PrefixDelete(prefix []byte) (int, error) { + if l.trackDeletedTries != nil { + if err := l.trackDeletedTries.Put(prefix, binary.BigEndian.AppendUint64(nil, l.count)); err != nil { + return 0, fmt.Errorf("failed to track deleted trie %x: %w", prefix, err) + } + } + accounts, err := trie.New(trie.StateTrieID(l.root), l.triedb) if err != nil { return 0, err @@ -243,11 +249,6 @@ func (l *Legacy) PrefixDelete(prefix []byte) (int, error) { if origin == types.EmptyRootHash { return 0, nil } - if l.trackDeletedTries != nil { - if err := l.trackDeletedTries.Put(prefix, binary.BigEndian.AppendUint64(nil, l.count)); err != nil { - return 0, fmt.Errorf("failed to track deleted trie %x: %w", prefix, err) - } - } nodes := trienode.NewMergedNodeSet() _, leafs, _, set, err := slowDeleteStorage(l.triedb, l.root, common.BytesToHash(prefix), origin) if err != nil { From 4a1b8d51bdc977ef4f3f9ac3d3d90fb8c470a267 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 15:13:46 -0800 Subject: [PATCH 292/307] add firewood backend --- go.mod | 3 +++ go.sum | 2 ++ plugin/evm/firewood_db | Bin 0 -> 395613 bytes plugin/evm/reprocess_backend_test.go | 31 ++++++++++++++++----------- plugin/evm/reprocess_test.go | 10 ++++++--- shim/fw/firewood.go | 18 ++++++++++++++++ 6 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 plugin/evm/firewood_db create mode 100644 shim/fw/firewood.go diff --git a/go.mod b/go.mod index d0271965f6..a742e59d34 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/Yiling-J/theine-go v0.6.0 github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d + github.com/ava-labs/firewood/ffi/v2 v2.0.0-20250115224253-5544080dfc47 github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -140,3 +141,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) + +// replace github.com/ava-labs/firewood/ffi/v2 => /Users/darioush.jalali/git2/firewood/ffi diff --git a/go.sum b/go.sum index 3703a8a15b..1e3e977264 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d h1:iPlsqC9pIy4emCo8wyI/VmVmfljpzmw58ZqahVdcehI= github.com/ava-labs/avalanchego v1.12.2-0.20241224181600-fade5be3051d/go.mod h1:dKawab3nXqwI7ZcOFatTOv//l1V0t8MRBnhXoOqbN4E= +github.com/ava-labs/firewood/ffi/v2 v2.0.0-20250115224253-5544080dfc47 h1:us8sfppAqnSwNLnvG8AKhxHkQijYkoZMlg79Q3fyU3k= +github.com/ava-labs/firewood/ffi/v2 v2.0.0-20250115224253-5544080dfc47/go.mod h1:BvKhmb9EeMKscnTWGQFqnT5vdqEMneG/hR6Sns7GBxg= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= diff --git a/plugin/evm/firewood_db b/plugin/evm/firewood_db new file mode 100644 index 0000000000000000000000000000000000000000..bb7f8254840541e1078f8439db37aa561030e370 GIT binary patch literal 395613 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�b>P+w_j#Rrzaz`l#@%dzUY~a* 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 literal 0 HcmV?d00001 diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index f0273cbcff..b37cbb315c 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "math/big" "net" + "os" "testing" "github.com/ava-labs/avalanchego/database" @@ -24,9 +25,11 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" warpcontract "github.com/ava-labs/coreth/precompile/contracts/warp" + "github.com/ava-labs/coreth/shim/fw" "github.com/ava-labs/coreth/shim/merkledb" "github.com/ava-labs/coreth/shim/nomt" "github.com/ava-labs/coreth/triedb" + firewood "github.com/ava-labs/firewood/ffi/v2" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -131,19 +134,7 @@ func getBackend(t *testing.T, name string, blocksCount int, dbs dbs) *reprocessB require.NoError(t, err) require.Len(t, blocks, blocksCount) - var ( - merkleDB xmerkledb.MerkleDB - kvBackend triedb.KVBackend - ) - if name == "merkledb" { - merkleDB = getMerkleDB(t, dbs.merkledb) - kvBackend = merkledb.NewMerkleDB(merkleDB) - } - if name == "nomt" { - conn, err := net.Dial("unix", socketPath) - require.NoError(t, err) - kvBackend = nomt.New(conn) - } + kvBackend := getKVBackend(t, name, dbs.merkledb) return &reprocessBackend{ Genesis: g, Engine: engine, @@ -204,6 +195,11 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs } } +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil || !os.IsNotExist(err) +} + func getKVBackend(t *testing.T, name string, merkleKVStore database.Database) triedb.KVBackend { if name == "merkledb" { return merkledb.NewMerkleDB(getMerkleDB(t, merkleKVStore)) @@ -213,5 +209,14 @@ func getKVBackend(t *testing.T, name string, merkleKVStore database.Database) tr require.NoError(t, err) return nomt.New(conn) } + if name == "firewood" { + var fwdb firewood.Firewood + if fileExists(firewoodDBFile) { + fwdb = firewood.OpenDatabase(firewoodDBFile) + } else { + fwdb = firewood.CreateDatabase(firewoodDBFile) + } + return &fw.Firewood{Firewood: fwdb} + } return nil } diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index a338bc14cd..ba93dd0b64 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -67,6 +67,9 @@ var ( intermediateWriteBufferSizeKB = 1024 intermediateWriteBatchSizeKB = 256 + // firewood options + firewoodDBFile = "firewood_db" + // ipc options socketPath = "/tmp/rust_socket" ) @@ -93,11 +96,12 @@ func TestMain(m *testing.M) { flag.StringVar(&readCacheBackend, "readCacheBackend", readCacheBackend, "read cache backend (theine, fastcache, otter, none)") flag.Uint64Var(&writeCacheSize, "writeCacheSize", writeCacheSize, "write cache size in items") flag.StringVar(&socketPath, "socketPath", socketPath, "socket path") - flag.StringVar(&storageBackend, "storageBackend", storageBackend, "storage backend (none, legacy, merkledb, nomt)") + flag.StringVar(&storageBackend, "storageBackend", storageBackend, "storage backend (none, legacy, merkledb, nomt, firewood)") flag.IntVar(&commitEachBlocks, "commitEachBlocks", commitEachBlocks, "commit each N blocks") flag.IntVar(&commitEachTxs, "commitEachTxs", commitEachTxs, "commit each N transactions") flag.BoolVar(&forceStartWithMismatch, "forceStartWithMismatch", forceStartWithMismatch, "force start with mismatch") flag.BoolVar(&trackDeletedTries, "trackDeletedTries", trackDeletedTries, "track deleted tries (detect re-use of SELFDESTRUCTed accounts)") + flag.StringVar(&firewoodDBFile, "firewoodDBFile", firewoodDBFile, "firewood DB file") // merkledb options flag.IntVar(&merkleDBBranchFactor, "merkleDBBranchFactor", merkleDBBranchFactor, "merkleDB branch factor") @@ -402,7 +406,7 @@ func CleanupOnInterrupt(cleanup func()) { func TestReprocessGenesis(t *testing.T) { // nomt commented out as needs separate process to function - for _, backend := range []string{"merkledb", "legacy" /* , "nomt" */} { + for _, backend := range []string{"merkledb", "legacy", "firewood" /* , "nomt" */} { t.Run(backend, func(t *testing.T) { testReprocessGenesis(t, backend) }) } } @@ -435,7 +439,7 @@ func TestReprocessMainnetBlocks(t *testing.T) { startBlock++ } - for _, backendName := range []string{"nomt", "merkledb", "legacy"} { + for _, backendName := range []string{"nomt", "merkledb", "legacy", "firewood"} { t.Run(backendName, func(t *testing.T) { backend := getMainnetBackend(t, backendName, source, dbs) lastHash, lastRoot = reprocess(t, backend, lastHash, lastRoot, startBlock, endBlock) diff --git a/shim/fw/firewood.go b/shim/fw/firewood.go new file mode 100644 index 0000000000..61310c9149 --- /dev/null +++ b/shim/fw/firewood.go @@ -0,0 +1,18 @@ +package fw + +import ( + "github.com/ava-labs/coreth/triedb" + firewood "github.com/ava-labs/firewood/ffi/v2" +) + +var _ triedb.KVBackend = &Firewood{} + +type Firewood struct { + firewood.Firewood +} + +// PrefixDelete is a no-op as firewood implements deletes as prefix deletes. +// This means when the account is deleted, all related storage is also deleted. +func (f *Firewood) PrefixDelete(prefix []byte) (int, error) { + return 0, nil +} From 419884d959511be6351e8ba99fdc844db9a0a9d1 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 15:16:29 -0800 Subject: [PATCH 293/307] remove test db --- plugin/evm/firewood_db | Bin 395613 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 plugin/evm/firewood_db diff --git a/plugin/evm/firewood_db b/plugin/evm/firewood_db deleted file mode 100644 index bb7f8254840541e1078f8439db37aa561030e370..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 395613 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�b>P+w_j#Rrzaz`l#@%dzUY~a* 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 From f6cebacf21507603d27497bf11eab3d2bbe308ff Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 15:23:18 -0800 Subject: [PATCH 294/307] merge fixes --- plugin/evm/post_processing_test.go | 3 ++- plugin/evm/reprocess_test.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index a7effe220b..836898d792 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/state/snapshot" + "github.com/ava-labs/coreth/plugin/evm/database" "github.com/ava-labs/coreth/shim/legacy" "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" @@ -231,7 +232,7 @@ func TestPostProcess(t *testing.T) { // install selfdestruct re-use detection if requested if trackDeletedTries { store := prefixdb.New([]byte("trackDeletedTries"), dbs.metadata) - legacyStore.TrackDeletedTries(rawdb.NewDatabase(Database{store})) + legacyStore.TrackDeletedTries(rawdb.NewDatabase(database.WrapDatabase(store))) t.Logf("Enabled trackDeletedTries") it := store.NewIterator() diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index ba93dd0b64..2e72305034 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/coreth/core/rawdb" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/plugin/evm/atomic" + evmdatabase "github.com/ava-labs/coreth/plugin/evm/database" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -314,11 +315,11 @@ func openDBs(t *testing.T) dbs { var chaindb ethdb.Database if len(prefix) > 0 { chaindb = &prefixReader{ - Database: rawdb.NewDatabase(Database{base}), + Database: rawdb.NewDatabase(evmdatabase.WrapDatabase(base)), prefix: prefix, } } else { - chaindb = rawdb.NewDatabase(Database{prefixdb.New(ethDBPrefix, base)}) + chaindb = rawdb.NewDatabase(evmdatabase.WrapDatabase(prefixdb.New(ethDBPrefix, base))) } return dbs{ metadata: prefixdb.New(reprocessMetadataPrefix, base), From efcbce37d00ef873034f1f6596649ee3fd260cce Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 15:52:51 -0800 Subject: [PATCH 295/307] handle empty batches --- shim/fw/firewood.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shim/fw/firewood.go b/shim/fw/firewood.go index 61310c9149..56cc7cdff3 100644 --- a/shim/fw/firewood.go +++ b/shim/fw/firewood.go @@ -16,3 +16,13 @@ type Firewood struct { func (f *Firewood) PrefixDelete(prefix []byte) (int, error) { return 0, nil } + +// Update updates the trie with the provided key-value pairs. +// Firewood ffi does not accept empty batches, so if the keys are empty, the +// root is returned. +func (f *Firewood) Update(ks, vs [][]byte) ([]byte, error) { + if len(ks) == 0 { + return f.Firewood.Root(), nil + } + return f.Firewood.Update(ks, vs) +} From 9144240140fb2d8f71dfba4049d10c1919ac88c9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 16:39:19 -0800 Subject: [PATCH 296/307] add vm option --- eth/backend.go | 4 ++++ eth/ethconfig/config.go | 4 ++++ plugin/evm/config/config.go | 3 +++ plugin/evm/reprocess_backend_test.go | 6 ------ plugin/evm/vm.go | 17 +++++++++++++++++ 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 79dce86db3..1494d6fc11 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -55,6 +55,7 @@ import ( "github.com/ava-labs/coreth/node" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/rpc" + "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -228,6 +229,9 @@ func New( StateScheme: scheme, } ) + if config.KVBackend != nil { + cacheConfig.KeyValueDB = &triedb.KeyValueConfig{KVBackend: config.KVBackend} + } if err := eth.precheckPopulateMissingTries(); err != nil { return nil, err diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index f7697112ef..25f465d508 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -35,6 +35,7 @@ import ( "github.com/ava-labs/coreth/eth/gasprice" "github.com/ava-labs/coreth/miner" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/triedb" "github.com/ethereum/go-ethereum/common" ) @@ -107,6 +108,9 @@ type Config struct { SnapshotCache int Preimages bool + // KVBackend + KVBackend triedb.KVBackend + // AcceptedCacheSize is the depth of accepted headers cache and accepted // logs cache at the accepted tip. AcceptedCacheSize int diff --git a/plugin/evm/config/config.go b/plugin/evm/config/config.go index 3104b0faa9..0d812961d0 100644 --- a/plugin/evm/config/config.go +++ b/plugin/evm/config/config.go @@ -227,6 +227,9 @@ type Config struct { // RPC settings HttpBodyLimit uint64 `json:"http-body-limit"` + + // Firewood settings + FirewoodDBFile string `json:"firewood-db-file"` } // TxPoolConfig contains the transaction pool config to be passed diff --git a/plugin/evm/reprocess_backend_test.go b/plugin/evm/reprocess_backend_test.go index b37cbb315c..c47493d4d8 100644 --- a/plugin/evm/reprocess_backend_test.go +++ b/plugin/evm/reprocess_backend_test.go @@ -6,7 +6,6 @@ import ( "encoding/json" "math/big" "net" - "os" "testing" "github.com/ava-labs/avalanchego/database" @@ -195,11 +194,6 @@ func getMainnetBackend(t *testing.T, name string, source ethdb.Database, dbs dbs } } -func fileExists(filename string) bool { - _, err := os.Stat(filename) - return err == nil || !os.IsNotExist(err) -} - func getKVBackend(t *testing.T, name string, merkleKVStore database.Database) triedb.KVBackend { if name == "merkledb" { return merkledb.NewMerkleDB(getMerkleDB(t, merkleKVStore)) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 179c6e7768..b5f5521429 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -23,6 +23,7 @@ import ( "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/upgrade" avalanchegoConstants "github.com/ava-labs/avalanchego/utils/constants" + firewood "github.com/ava-labs/firewood/ffi/v2" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/coreth/consensus/dummy" @@ -43,6 +44,7 @@ import ( "github.com/ava-labs/coreth/plugin/evm/atomic" "github.com/ava-labs/coreth/plugin/evm/config" "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/ava-labs/coreth/shim/fw" "github.com/ava-labs/coreth/triedb" "github.com/ava-labs/coreth/triedb/hashdb" "github.com/ava-labs/coreth/utils" @@ -506,6 +508,16 @@ func (vm *VM) Initialize( vm.ethConfig.AcceptedCacheSize = vm.config.AcceptedCacheSize vm.ethConfig.TransactionHistory = vm.config.TransactionHistory vm.ethConfig.SkipTxIndexing = vm.config.SkipTxIndexing + if file := vm.config.FirewoodDBFile; file != "" { + var fwdb firewood.Firewood + if fileExists(file) { + fwdb = firewood.OpenDatabase(file) + } else { + fwdb = firewood.CreateDatabase(file) + } + vm.ethConfig.KVBackend = &fw.Firewood{Firewood: fwdb} + log.Warn("Using Firewood database (experimental)", "file", file) + } // Create directory for offline pruning if len(vm.ethConfig.OfflinePruningDataDirectory) != 0 { @@ -1875,3 +1887,8 @@ func (vm *VM) newExportTx( return tx, nil } + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil || !os.IsNotExist(err) +} From 5865517b2c0c24e85c0007da53000d815a17eb18 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 16:46:53 -0800 Subject: [PATCH 297/307] add log timer --- plugin/evm/reprocess_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index 2e72305034..a0ccd085d2 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -9,6 +9,7 @@ import ( "sync" "syscall" "testing" + "time" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/leveldb" @@ -558,6 +559,7 @@ func reprocess( bc.SetSnapWriter(tapeRecorder) } + lastLogTime := time.Now() for i := start; i <= stop; i++ { block := backend.GetBlock(i) isApricotPhase5 := backend.Genesis.Config.IsApricotPhase5(block.Time()) @@ -581,7 +583,9 @@ func reprocess( tapeRecorder.Reset() } else { if i%uint64(logEach) == 0 { - t.Logf("Block: %d, Txs: %d (+ %d atomic), Parent State: %s", i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) + took := time.Since(lastLogTime) + lastLogTime = time.Now() + t.Logf("(%v) Block: %d, Txs: %d (+ %d atomic), Parent State: %s", took.Truncate(time.Millisecond), i, len(block.Transactions()), len(atomicTxs), lastRoot.TerminalString()) } } From 133618c2a1edea569a02b36fb7e177423a9e0627 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 15 Jan 2025 17:28:27 -0800 Subject: [PATCH 298/307] reduce logs --- plugin/evm/post_processing_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 836898d792..6863a9d6a4 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -612,9 +612,9 @@ func processAccountDeletes(t *testing.T, ks, vs [][]byte) ([][]byte, [][]byte, m vs[outIdx] = vs[i] outIdx++ } - if outIdx < len(ks) { - t.Logf("Removed %d updates from pending batch", len(ks)-outIdx) - } + //if outIdx < len(ks) { + // t.Logf("Removed %d updates from pending batch", len(ks)-outIdx) + //} return ks[:outIdx], vs[:outIdx], accsDeleted } From 4f37033799a245c51e36e6c447426fa99e783c8c Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 21 Jan 2025 09:30:59 -0800 Subject: [PATCH 299/307] only verify root if there's no write cache --- plugin/evm/post_processing_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index 6863a9d6a4..b7c9dfddb5 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -488,7 +488,7 @@ func TestPostProcess(t *testing.T) { require.NoError(t, b.Write()) - if storageBackend == "legacy" { + if storageBackend == "legacy" && writeCacheSize == 0 { require.Equal(t, storageRoot, block.Root(), "Root mismatch") } } From 27cd7973e03651a0faad6e2a8a331cc9fea9d10d Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 21 Jan 2025 09:44:10 -0800 Subject: [PATCH 300/307] try --- plugin/evm/post_processing_test.go | 5 ++++ shim/legacy/legacy.go | 44 ++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index b7c9dfddb5..aa0748fbba 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -242,6 +242,11 @@ func TestPostProcess(t *testing.T) { require.NoError(t, it.Error()) it.Release() } + if writeCacheSize > 0 { + // Account roots will differ if we buffer writes. This tracks the actual + // roots (different from what is put in the trie in the tape). + legacyStore.DisableAccountRootCheck() + } storage = legacyStore } require.Equal(t, lastRoot, common.BytesToHash(storage.Root()), "Root mismatch") diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 61746961e4..6f7865a48d 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -16,11 +16,12 @@ import ( var _ triedb.KVBackend = &Legacy{} type Legacy struct { - triedb *triedb.Database - root common.Hash - count uint64 - dereference bool - trackDeletedTries ethdb.KeyValueStore + triedb *triedb.Database + root common.Hash + count uint64 + dereference bool + trackDeletedTries ethdb.KeyValueStore + accountRootCheckDisabled bool } func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bool) *Legacy { @@ -36,6 +37,10 @@ func (l *Legacy) TrackDeletedTries(db ethdb.KeyValueStore) { l.trackDeletedTries = db } +func (l *Legacy) DisableAccountRootCheck() { + l.accountRootCheckDisabled = true +} + func getAccountRoot(tr *trie.Trie, accHash common.Hash) (common.Hash, error) { root := types.EmptyRootHash accBytes, err := tr.Get(accHash[:]) @@ -52,6 +57,27 @@ func getAccountRoot(tr *trie.Trie, accHash common.Hash) (common.Hash, error) { return root, nil } +func setAccountRoot(tr *trie.Trie, accHash common.Hash, root common.Hash) error { + accBytes, err := tr.Get(accHash[:]) + if err != nil { + return err + } + var acc types.StateAccount + if len(accBytes) == 0 { + return fmt.Errorf("account %x not found", accHash) + } + if err := rlp.DecodeBytes(accBytes, &acc); err != nil { + return fmt.Errorf("failed to decode account: %w", err) + } + acc.Root = root + accBytes, err = rlp.EncodeToBytes(&acc) + if err != nil { + return fmt.Errorf("failed to encode account: %w", err) + } + tr.MustUpdate(accHash[:], accBytes) + return nil +} + func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { // Collect all nodes that are modified during the update // Defined here so we can process storage deletes @@ -152,7 +178,13 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { return nil, err } if root != tr.Hash() { - return nil, fmt.Errorf("account %x trie root mismatch (%x != %x)", accHash, root, tr.Hash()) + if l.accountRootCheckDisabled { + if err := setAccountRoot(accounts, accHash, tr.Hash()); err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("account %x trie root mismatch (%x != %x)", accHash, root, tr.Hash()) + } } } From f50879b4332eda96a92af2266b3d0f99238db282 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 21 Jan 2025 09:46:29 -0800 Subject: [PATCH 301/307] try --- shim/legacy/legacy.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index 6f7865a48d..b497088c9d 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -64,7 +64,10 @@ func setAccountRoot(tr *trie.Trie, accHash common.Hash, root common.Hash) error } var acc types.StateAccount if len(accBytes) == 0 { - return fmt.Errorf("account %x not found", accHash) + if root == types.EmptyRootHash { + return nil + } + return fmt.Errorf("account %x not found (wanted to set root to %x)", accHash, root) } if err := rlp.DecodeBytes(accBytes, &acc); err != nil { return fmt.Errorf("failed to decode account: %w", err) From 7d8a3afc2a8980a9b88e9afa7250cb7a33534235 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 21 Jan 2025 09:57:33 -0800 Subject: [PATCH 302/307] try --- shim/legacy/legacy.go | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index b497088c9d..ed2f06b891 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -63,14 +63,10 @@ func setAccountRoot(tr *trie.Trie, accHash common.Hash, root common.Hash) error return err } var acc types.StateAccount - if len(accBytes) == 0 { - if root == types.EmptyRootHash { - return nil + if len(accBytes) > 0 { + if err := rlp.DecodeBytes(accBytes, &acc); err != nil { + return fmt.Errorf("failed to decode account: %w", err) } - return fmt.Errorf("account %x not found (wanted to set root to %x)", accHash, root) - } - if err := rlp.DecodeBytes(accBytes, &acc); err != nil { - return fmt.Errorf("failed to decode account: %w", err) } acc.Root = root accBytes, err = rlp.EncodeToBytes(&acc) @@ -170,7 +166,30 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { if len(v) == 0 { accounts.MustDelete(k) } else { + var ( + root common.Hash + shouldSetRoot bool + ) + if l.accountRootCheckDisabled { + // If the trie is updated, the root will be set below + if _, ok := tries[common.BytesToHash(k[:32])]; !ok { + // Otherwise, avoid changing the root from it's current value + r, err := getAccountRoot(accounts, common.BytesToHash(k[:32])) + if err != nil { + return nil, err + } + root = r + shouldSetRoot = true + } + } + accounts.MustUpdate(k, v) + + if shouldSetRoot { + if err := setAccountRoot(accounts, common.BytesToHash(k[:32]), root); err != nil { + return nil, err + } + } } } From 04791e37e1377a8c2ad685e071a1585f394edadb Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 22 Jan 2025 12:22:08 -0800 Subject: [PATCH 303/307] ensure no loop in legacy --- shim/legacy/legacy.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index ed2f06b891..d8e6df67a8 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -217,7 +217,10 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { if set != nil { nodes.Merge(set) } - + if l.root == next { + fmt.Println("::: root is the same", l.root, next) + return next[:], nil + } if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { return nil, err } @@ -319,6 +322,10 @@ func (l *Legacy) PrefixDelete(prefix []byte) (int, error) { if set != nil { nodes.Merge(set) } + if l.root == next { + fmt.Println("::: root is the same", l.root, next) + return leafs, nil + } if err := l.triedb.Update(next, l.root, l.count, nodes, nil); err != nil { return 0, err } From 01c7772584ff493f01e9850e2dd83cef47303804 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 22 Jan 2025 15:58:21 -0800 Subject: [PATCH 304/307] fix empty in geth --- shim/legacy/legacy.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shim/legacy/legacy.go b/shim/legacy/legacy.go index d8e6df67a8..5e03996c99 100644 --- a/shim/legacy/legacy.go +++ b/shim/legacy/legacy.go @@ -22,6 +22,7 @@ type Legacy struct { dereference bool trackDeletedTries ethdb.KeyValueStore accountRootCheckDisabled bool + needsCommit bool } func New(triedb *triedb.Database, root common.Hash, count uint64, dereference bool) *Legacy { @@ -228,10 +229,15 @@ func (l *Legacy) Update(ks, vs [][]byte) ([]byte, error) { // TODO: fix hashdb scheme later l.root = next l.count++ + l.needsCommit = true return next[:], nil } func (l *Legacy) Commit(rootBytes []byte) error { + if !l.needsCommit { + fmt.Println("::: no need to commit") + return nil + } root := common.BytesToHash(rootBytes) return l.triedb.Commit(root, false) } From 79a46de3112861fdc0d7a94d01c4cb2224d601a3 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 22 Jan 2025 16:18:54 -0800 Subject: [PATCH 305/307] add histogram for snap val lens --- plugin/evm/reprocess_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugin/evm/reprocess_test.go b/plugin/evm/reprocess_test.go index a0ccd085d2..dcde43cfc1 100644 --- a/plugin/evm/reprocess_test.go +++ b/plugin/evm/reprocess_test.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + "github.com/valyala/histogram" "golang.org/x/crypto/sha3" ) @@ -623,6 +624,9 @@ func TestCheckSnapshot(t *testing.T) { func checkSnapshot(t *testing.T, db ethdb.Database, log bool) (int, int) { t.Helper() + accountHist := histogram.NewFast() + storageHist := histogram.NewFast() + it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) defer it.Release() accounts := 0 @@ -631,6 +635,7 @@ func checkSnapshot(t *testing.T, db ethdb.Database, log bool) (int, int) { continue } accounts++ + accountHist.Update(float64(len(it.Value()))) if log { t.Logf("Snapshot (account): %x, %x\n", it.Key(), it.Value()) } @@ -644,10 +649,17 @@ func checkSnapshot(t *testing.T, db ethdb.Database, log bool) (int, int) { continue } storages++ + storageHist.Update(float64(len(it2.Value()))) if log { t.Logf("Snapshot (storage): %x, %x", it2.Key(), it2.Value()) } } + + quantiles := []float64{0.5, 0.9, 0.99, 0.999} + for _, q := range quantiles { + t.Logf("Snapshot quantile %v (account): %.2f", q, accountHist.Quantile(q)) + t.Logf("Snapshot quantile %v (storage): %.2f", q, storageHist.Quantile(q)) + } return accounts, storages } From 20c198b1732c19432291537faee3f803650283d9 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Sat, 25 Jan 2025 21:30:37 -0800 Subject: [PATCH 306/307] dedup --- shim/nomt/nomt.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/shim/nomt/nomt.go b/shim/nomt/nomt.go index 2820208b01..00bf9530e7 100644 --- a/shim/nomt/nomt.go +++ b/shim/nomt/nomt.go @@ -107,13 +107,22 @@ func (n *Nomt) Update(ks, vs [][]byte) ([]byte, error) { req := &nomt.Request{ Request: &nomt.Request_Update{ Update: &nomt.UpdateRequest{ - Items: make([]*nomt.UpdateRequestItem, len(ks)), + Items: make([]*nomt.UpdateRequestItem, 0, len(ks)), }, }, } + seen := make(map[string]struct{}) for i, k := range ks { - req.GetUpdate().Items[i] = &nomt.UpdateRequestItem{Key: k, Value: vs[i]} + _, found := seen[string(k)] + if found { + continue + } + req.GetUpdate().Items = append( + req.GetUpdate().Items, + &nomt.UpdateRequestItem{Key: k, Value: vs[i]}, + ) + seen[string(k)] = struct{}{} } resp, err := n.response(req) From 633d9074aaf4ff3759785d5381e032d8d00a0687 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 31 Jan 2025 09:01:34 -0800 Subject: [PATCH 307/307] add kv len asserts --- plugin/evm/post_processing_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugin/evm/post_processing_test.go b/plugin/evm/post_processing_test.go index aa0748fbba..6a253b7734 100644 --- a/plugin/evm/post_processing_test.go +++ b/plugin/evm/post_processing_test.go @@ -430,6 +430,18 @@ func TestPostProcess(t *testing.T) { } now := time.Now() + // Verify that the key value lengths are within reason + for _, k := range evictedKs { + if len(k) != 32 && len(k) != 64 { + panic(fmt.Sprintf("Invalid key length: %d", len(k))) + } + } + for _, v := range evictedVs { + if len(v) > 256 { + panic(fmt.Sprintf("Invalid value length: %d", len(v))) + } + } + // Get state commitment from storage backend storageRootBytes, err := storage.Update(evictedKs, evictedVs) require.NoError(t, err)