From cd44e361e024e2ee8b0a2130119ba0c3f38a7777 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 15 Oct 2024 19:12:49 -0700 Subject: [PATCH 1/4] use triedb config for reference root --- cmd/evm/internal/t8ntool/execution.go | 4 +-- cmd/evm/runner.go | 2 +- core/blockchain.go | 14 +++++------ core/chain_makers.go | 2 +- core/genesis.go | 2 +- core/state/state_test.go | 8 +++--- core/state/statedb.go | 20 ++++++--------- core/state/statedb_fuzz_test.go | 2 +- core/state/statedb_test.go | 24 +++++++++--------- core/txpool/blobpool/blobpool_test.go | 12 ++++----- eth/api_debug_test.go | 6 ++--- eth/state_accessor.go | 13 +++++++--- tests/state_test_util.go | 4 +-- triedb/database.go | 11 --------- triedb/hashdb/database.go | 35 +++++++++++---------------- 15 files changed, 70 insertions(+), 89 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index f44e01d7ac..29eb68b5a2 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -324,7 +324,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, statedb.AddBalance(pre.Env.Coinbase, uint256.MustFromBig(minerReward)) } // Commit block - root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), false) + root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) } @@ -366,7 +366,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(0, false, false) + root, _ := statedb.Commit(0, false) statedb, _ = state.New(root, sdb, nil) return statedb } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 48902609ba..2ff5bd962d 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -281,7 +281,7 @@ func runCmd(ctx *cli.Context) error { output, leftOverGas, stats, err := timedExec(bench, execFunc) if ctx.Bool(DumpFlag.Name) { - statedb.Commit(genesisConfig.Number, true, false) + statedb.Commit(genesisConfig.Number, true) fmt.Println(string(statedb.Dump(nil))) } diff --git a/core/blockchain.go b/core/blockchain.go index a1272336e3..0eb5c3b243 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -196,10 +196,11 @@ type CacheConfig struct { // triedbConfig derives the configures for trie database. func (c *CacheConfig) triedbConfig() *triedb.Config { config := &triedb.Config{Preimages: c.Preimages} - if c.StateScheme == rawdb.HashScheme { + if c.StateScheme == rawdb.HashScheme || c.StateScheme == "" { config.HashDB = &hashdb.Config{ CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, StatsPrefix: trieCleanCacheStatsNamespace, + ReferenceRoot: true, } } if c.StateScheme == rawdb.PathScheme { @@ -1222,9 +1223,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // diff layer for the block. var err error if bc.snaps == nil { - _, err = state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), true) + _, err = state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number())) } else { - _, err = state.CommitWithSnap(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash(), true) + _, err = state.CommitWithSnap(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.snaps, block.Hash(), block.ParentHash()) } if err != nil { return err @@ -1756,9 +1757,9 @@ func (bc *BlockChain) reprocessBlock(parent *types.Block, current *types.Block) // If snapshots are enabled, call CommitWithSnaps to explicitly create a snapshot // diff layer for the block. if bc.snaps == nil { - return statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), false) + return statedb.Commit(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number())) } - return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash(), false) + return statedb.CommitWithSnap(current.NumberU64(), bc.chainConfig.IsEIP158(current.Number()), bc.snaps, current.Hash(), current.ParentHash()) } // initSnapshot instantiates a Snapshot instance and adds it to [bc] @@ -1899,8 +1900,7 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error // Flatten snapshot if initialized, holding a reference to the state root until the next block // is processed. if err := bc.flattenSnapshot(func() error { - triedb.Reference(root, common.Hash{}) - if previousRoot != (common.Hash{}) { + if previousRoot != (common.Hash{}) && previousRoot != root { triedb.Dereference(previousRoot) } previousRoot = root diff --git a/core/chain_makers.go b/core/chain_makers.go index ad5c106cf3..6b8467dc3c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -300,7 +300,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } // Write state changes to db - root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), false) + root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number)) if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } diff --git a/core/genesis.go b/core/genesis.go index 1ee615a59a..e08f186981 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -328,7 +328,7 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo } } - statedb.Commit(0, false, false) + 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 { diff --git a/core/state/state_test.go b/core/state/state_test.go index e0c3cb41ac..24313429db 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -68,7 +68,7 @@ func TestDump(t *testing.T) { // write some of them to the trie s.state.updateStateObject(obj1) s.state.updateStateObject(obj2) - root, _ := s.state.Commit(0, false, false) + root, _ := s.state.Commit(0, false) // check that DumpToCollector contains the state objects that are in trie s.state, _ = New(root, tdb, nil) @@ -127,7 +127,7 @@ func TestIterativeDump(t *testing.T) { // write some of them to the trie s.state.updateStateObject(obj1) s.state.updateStateObject(obj2) - root, _ := s.state.Commit(0, false, false) + root, _ := s.state.Commit(0, false) s.state, _ = New(root, tdb, nil) b := &bytes.Buffer{} @@ -153,7 +153,7 @@ func TestNull(t *testing.T) { var value common.Hash s.state.SetState(address, common.Hash{}, value) - s.state.Commit(0, false, false) + s.state.Commit(0, false) if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) { t.Errorf("expected empty current value, got %x", value) @@ -225,7 +225,7 @@ func TestSnapshot2(t *testing.T) { so0.deleted = false state.setStateObject(so0) - root, _ := state.Commit(0, false, false) + root, _ := state.Commit(0, false) state, _ = New(root, state.db, nil) // and one with deleted == true diff --git a/core/state/statedb.go b/core/state/statedb.go index 9779bec6fe..e73cf9accb 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1217,14 +1217,14 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A } // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, referenceRoot bool) (common.Hash, error) { - return s.commit(block, deleteEmptyObjects, nil, common.Hash{}, common.Hash{}, referenceRoot) +func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) { + return s.commit(block, deleteEmptyObjects, nil, common.Hash{}, common.Hash{}) } // CommitWithSnap writes the state to the underlying in-memory trie database and // generates a snapshot layer for the newly committed state. -func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) { - return s.commit(block, deleteEmptyObjects, snaps, blockHash, parentHash, referenceRoot) +func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash) (common.Hash, error) { + return s.commit(block, deleteEmptyObjects, snaps, blockHash, parentHash) } // Once the state is committed, tries cached in stateDB (including account @@ -1234,7 +1234,7 @@ func (s *StateDB) CommitWithSnap(block uint64, deleteEmptyObjects bool, snaps *s // // The associated block number of the state transition is also provided // for more chain context. -func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash, referenceRoot bool) (common.Hash, error) { +func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot.Tree, blockHash, parentHash common.Hash) (common.Hash, error) { // Short circuit in case any database failure occurred earlier. if s.dbErr != nil { return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) @@ -1343,14 +1343,8 @@ func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot. if root != origin { start := time.Now() set := triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete) - if referenceRoot { - if err := s.db.TrieDB().UpdateAndReferenceRoot(root, origin, block, nodes, set); err != nil { - return common.Hash{}, err - } - } else { - if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil { - return common.Hash{}, err - } + if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil { + return common.Hash{}, err } s.originalRoot = root if metrics.EnabledExpensive { diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go index 47b22b104e..7bd3c0b602 100644 --- a/core/state/statedb_fuzz_test.go +++ b/core/state/statedb_fuzz_test.go @@ -233,7 +233,7 @@ func (test *stateTest) run() bool { } else { state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary } - nroot, err := state.Commit(0, true, false) // call commit at the block boundary + nroot, err := state.Commit(0, true) // call commit at the block boundary if err != nil { panic(err) } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 60d7ee3b41..29e91111ab 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -126,7 +126,7 @@ func TestIntermediateLeaks(t *testing.T) { } // Commit and cross check the databases. - transRoot, err := transState.Commit(0, false, false) + transRoot, err := transState.Commit(0, false) if err != nil { t.Fatalf("failed to commit transition state: %v", err) } @@ -134,7 +134,7 @@ func TestIntermediateLeaks(t *testing.T) { t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) } - finalRoot, err := finalState.Commit(0, false, false) + finalRoot, err := finalState.Commit(0, false) if err != nil { t.Fatalf("failed to commit final state: %v", err) } @@ -542,7 +542,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { func TestTouchDelete(t *testing.T) { s := newStateEnv() s.state.getOrNewStateObject(common.Address{}) - root, _ := s.state.Commit(0, false, false) + root, _ := s.state.Commit(0, false) s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap) snapshot := s.state.Snapshot() @@ -630,7 +630,7 @@ func TestCopyCommitCopy(t *testing.T) { t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval) } // Commit state, ensure states can be loaded from disk - root, _ := state.Commit(0, false, false) + root, _ := state.Commit(0, false) state, _ = New(root, tdb, nil) if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42) @@ -744,7 +744,7 @@ func TestCommitCopy(t *testing.T) { t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } // Copy the committed state database, the copied one is not functional. - state.Commit(0, true, false) + state.Commit(0, true) copied := state.Copy() if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(0)) != 0 { t.Fatalf("unexpected balance: have %v", balance) @@ -778,7 +778,7 @@ func TestDeleteCreateRevert(t *testing.T) { addr := common.BytesToAddress([]byte("so")) state.SetBalance(addr, uint256.NewInt(1)) - root, _ := state.Commit(0, false, false) + root, _ := state.Commit(0, false) state, _ = NewWithSnapshot(root, state.db, state.snap) // Simulate self-destructing in one transaction, then create-reverting in another @@ -790,7 +790,7 @@ func TestDeleteCreateRevert(t *testing.T) { state.RevertToSnapshot(id) // Commit the entire state and make sure we don't crash and have the correct state - root, _ = state.Commit(0, true, false) + root, _ = state.Commit(0, true) state, _ = NewWithSnapshot(root, state.db, state.snap) if state.getStateObject(addr) != nil { @@ -833,7 +833,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) { a2 := common.BytesToAddress([]byte("another")) state.SetBalance(a2, uint256.NewInt(100)) state.SetCode(a2, []byte{1, 2, 4}) - root, _ = state.Commit(0, false, false) + root, _ = state.Commit(0, false) t.Logf("root: %x", root) // force-flush tdb.Commit(root, false) @@ -857,7 +857,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) { } // Modify the state state.SetBalance(addr, uint256.NewInt(2)) - root, err := state.Commit(0, false, false) + root, err := state.Commit(0, false) if err == nil { t.Fatalf("expected error, got root :%x", root) } @@ -1053,7 +1053,7 @@ func TestFlushOrderDataLoss(t *testing.T) { state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s}) } } - root, err := state.Commit(0, false, false) + root, err := state.Commit(0, false) if err != nil { t.Fatalf("failed to commit state trie: %v", err) } @@ -1132,7 +1132,7 @@ func TestResetObject(t *testing.T) { state.CreateAccount(addr) state.SetBalance(addr, uint256.NewInt(2)) state.SetState(addr, slotB, common.BytesToHash([]byte{0x2})) - root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false) + root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}) // Ensure the original account is wiped properly snap := snaps.Snapshot(root) @@ -1163,7 +1163,7 @@ func TestDeleteStorage(t *testing.T) { value := common.Hash(uint256.NewInt(uint64(10 * i)).Bytes32()) state.SetState(addr, slot, value) } - root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false) + root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}) // Init phase done, create two states, one with snap and one without fastState, _ := New(root, db, snaps) slowState, _ := New(root, db, nil) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 27bf9f0eab..b7d0c06c2b 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -583,7 +583,7 @@ func TestOpenDrops(t *testing.T) { statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000)) statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000)) statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000)) - statedb.Commit(0, true, false) + statedb.Commit(0, true) chain := &testBlockChain{ config: testChainConfig, @@ -702,7 +702,7 @@ func TestOpenIndex(t *testing.T) { // Create a blob pool out of the pre-seeded data statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) statedb.AddBalance(addr, uint256.NewInt(1_000_000_000)) - statedb.Commit(0, true, false) + statedb.Commit(0, true) chain := &testBlockChain{ config: testChainConfig, @@ -804,7 +804,7 @@ func TestOpenHeap(t *testing.T) { statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000)) - statedb.Commit(0, true, false) + statedb.Commit(0, true) chain := &testBlockChain{ config: testChainConfig, @@ -884,7 +884,7 @@ func TestOpenCap(t *testing.T) { statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000)) statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000)) - statedb.Commit(0, true, false) + statedb.Commit(0, true) chain := &testBlockChain{ config: testChainConfig, @@ -1302,7 +1302,7 @@ func TestAdd(t *testing.T) { store.Put(blob) } } - statedb.Commit(0, true, false) + statedb.Commit(0, true) store.Close() // Create a blob pool out of the pre-seeded dats @@ -1375,7 +1375,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) { statedb.AddBalance(addr, uint256.NewInt(1_000_000_000)) pool.add(tx) } - statedb.Commit(0, true, false) + statedb.Commit(0, true) defer pool.Close() // Benchmark assembling the pending diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go index b4b626104c..218f686337 100644 --- a/eth/api_debug_test.go +++ b/eth/api_debug_test.go @@ -93,7 +93,7 @@ func TestAccountRange(t *testing.T) { m[addr] = true } } - root, _ := sdb.Commit(0, true, false) + root, _ := sdb.Commit(0, true) sdb, _ = state.New(root, statedb, nil) trie, err := statedb.OpenTrie(root) @@ -151,7 +151,7 @@ func TestEmptyAccountRange(t *testing.T) { st, _ = state.New(types.EmptyRootHash, statedb, nil) ) // Commit(although nothing to flush) and re-init the statedb - st.Commit(0, true, false) + st.Commit(0, true) st, _ = state.New(types.EmptyRootHash, statedb, nil) results := st.RawDump(&state.DumpConfig{ @@ -192,7 +192,7 @@ func TestStorageRangeAt(t *testing.T) { for _, entry := range storage { sdb.SetState(addr, *entry.Key, entry.Value) } - root, _ := sdb.Commit(0, false, false) + root, _ := sdb.Commit(0, false) sdb, _ = state.New(root, db, nil) // Check a few combinations of limit and start/end. diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 0508e7dc56..d7a25f20b5 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -40,6 +40,7 @@ import ( "github.com/ava-labs/subnet-evm/eth/tracers" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/triedb" + "github.com/ava-labs/subnet-evm/triedb/hashdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) @@ -73,13 +74,16 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u // The state is both for reading and writing, or it's unavailable in disk, // try to construct/recover the state over an ephemeral trie.Database for // isolating the live one. + // Use ReferenceRoot for ephemeral trie.Databases so reference counting + // logic is the same as a live trie.Database. + tdbConfig := &triedb.Config{HashDB: &hashdb.Config{ReferenceRoot: true}} if base != nil { if preferDisk { // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. // TODO(rjl493456442), clean cache is disabled to prevent memory leak, // please re-enable it for better performance. - database = state.NewDatabaseWithConfig(eth.chainDb, triedb.HashDefaults) + database = state.NewDatabaseWithConfig(eth.chainDb, tdbConfig) if statedb, err = state.New(block.Root(), database, nil); err == nil { log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number()) return statedb, noopReleaser, nil @@ -96,7 +100,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u // the internal junks created by tracing will be persisted into the disk. // TODO(rjl493456442), clean cache is disabled to prevent memory leak, // please re-enable it for better performance. - tdb = triedb.NewDatabase(eth.chainDb, triedb.HashDefaults) + tdb = triedb.NewDatabase(eth.chainDb, tdbConfig) database = state.NewDatabaseWithNodeDB(eth.chainDb, tdb) // If we didn't check the live database, do check state over ephemeral database, @@ -163,7 +167,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number()), true) + root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number())) if err != nil { return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", current.NumberU64(), current.Root().Hex(), err) @@ -172,7 +176,8 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u if err != nil { return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) } - // Note: In subnet-evm, the state reference is held by passing true to [statedb.Commit]. + // Note: In subnet-evm, the state reference is held because the triedb has + // ReferenceRoot enabled, as part of [triedb.Update] called from [statedb.Commit]. // Drop the parent state to prevent accumulating too many nodes in memory. if parent != (common.Hash{}) { tdb.Dereference(parent) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 7ba439accc..b6a59bb9b1 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -327,7 +327,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh state.StateDB.AddBalance(block.Coinbase(), new(uint256.Int)) // Commit state mutations into database. - root, _ = state.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number()), false) + root, _ = state.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number())) return state, root, err } @@ -475,7 +475,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(0, false, false) + root, _ := statedb.Commit(0, false) // If snapshot is requested, initialize the snapshotter and use it in state. var snaps *snapshot.Tree diff --git a/triedb/database.go b/triedb/database.go index 5383b57540..8042110275 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -148,17 +148,6 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n return db.backend.Update(root, parent, block, nodes, states) } -func (db *Database) UpdateAndReferenceRoot(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { - if db.preimages != nil { - db.preimages.commit(false) - } - hdb, ok := db.backend.(*hashdb.Database) - if ok { - return hdb.UpdateAndReferenceRoot(root, parent, block, nodes, states) - } - return db.backend.Update(root, parent, block, nodes, states) -} - // Commit iterates over all the children of a particular node, writes them out // to disk. As a side effect, all pre-images accumulated up to this point are // also written. diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go index 36b794abf2..dde3ce9b99 100644 --- a/triedb/hashdb/database.go +++ b/triedb/hashdb/database.go @@ -99,6 +99,7 @@ type cache interface { type Config struct { CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes StatsPrefix string // Prefix for cache stats (disabled if empty) + ReferenceRoot bool // Whether to reference the root node on update } // Defaults is the default setting for database if it's not specified. @@ -137,6 +138,8 @@ type Database struct { childrenSize common.StorageSize // Storage size of the external children tracking lock sync.RWMutex + + referenceRoot bool } // cachedNode is all the information we know about a single cached trie node @@ -174,10 +177,11 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas cleans = utils.NewMeteredCache(config.CleanCacheSize, config.StatsPrefix, cacheStatsUpdateFrequency) } return &Database{ - diskdb: diskdb, - resolver: resolver, - cleans: cleans, - dirties: make(map[common.Hash]*cachedNode), + diskdb: diskdb, + resolver: resolver, + cleans: cleans, + dirties: make(map[common.Hash]*cachedNode), + referenceRoot: config.ReferenceRoot, } } @@ -627,6 +631,8 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { // Update inserts the dirty nodes in provided nodeset into database and link the // account trie with multiple storage tries if necessary. +// If ReferenceRoot was enabled in the config, it will also add a reference from +// the root to the metaroot while holding the db's lock. func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { // Ensure the parent state is present and signal a warning if not. if parent != types.EmptyRootHash { @@ -637,26 +643,13 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n db.lock.Lock() defer db.lock.Unlock() - return db.update(root, parent, nodes) -} - -// UpdateAndReferenceRoot inserts the dirty nodes in provided nodeset into -// database and links the account trie with multiple storage tries if necessary, -// then adds a reference [from] root to the metaroot while holding the db's lock. -func (db *Database) UpdateAndReferenceRoot(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { - // Ensure the parent state is present and signal a warning if not. - if parent != types.EmptyRootHash { - if blob, _ := db.node(parent); len(blob) == 0 { - log.Error("parent state is not present") - } - } - db.lock.Lock() - defer db.lock.Unlock() - if err := db.update(root, parent, nodes); err != nil { return err } - db.reference(root, common.Hash{}) + + if db.referenceRoot { + db.reference(root, common.Hash{}) + } return nil } From 253e5c615717dfe65e3ce20e3b32cca66a51319f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 15 Oct 2024 20:00:45 -0700 Subject: [PATCH 2/4] use upstream state_accessor logic --- eth/state_accessor.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/eth/state_accessor.go b/eth/state_accessor.go index d7a25f20b5..89876b182a 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -40,7 +40,6 @@ import ( "github.com/ava-labs/subnet-evm/eth/tracers" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/triedb" - "github.com/ava-labs/subnet-evm/triedb/hashdb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) @@ -74,16 +73,13 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u // The state is both for reading and writing, or it's unavailable in disk, // try to construct/recover the state over an ephemeral trie.Database for // isolating the live one. - // Use ReferenceRoot for ephemeral trie.Databases so reference counting - // logic is the same as a live trie.Database. - tdbConfig := &triedb.Config{HashDB: &hashdb.Config{ReferenceRoot: true}} if base != nil { if preferDisk { // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. // TODO(rjl493456442), clean cache is disabled to prevent memory leak, // please re-enable it for better performance. - database = state.NewDatabaseWithConfig(eth.chainDb, tdbConfig) + database = state.NewDatabaseWithConfig(eth.chainDb, triedb.HashDefaults) if statedb, err = state.New(block.Root(), database, nil); err == nil { log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number()) return statedb, noopReleaser, nil @@ -100,7 +96,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u // the internal junks created by tracing will be persisted into the disk. // TODO(rjl493456442), clean cache is disabled to prevent memory leak, // please re-enable it for better performance. - tdb = triedb.NewDatabase(eth.chainDb, tdbConfig) + tdb = triedb.NewDatabase(eth.chainDb, triedb.HashDefaults) database = state.NewDatabaseWithNodeDB(eth.chainDb, tdb) // If we didn't check the live database, do check state over ephemeral database, @@ -176,9 +172,9 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u if err != nil { return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) } - // Note: In subnet-evm, the state reference is held because the triedb has - // ReferenceRoot enabled, as part of [triedb.Update] called from [statedb.Commit]. - // Drop the parent state to prevent accumulating too many nodes in memory. + // Hold the state reference and also drop the parent state + // to prevent accumulating too many nodes in memory. + tdb.Reference(root, common.Hash{}) if parent != (common.Hash{}) { tdb.Dereference(parent) } From a5b5caadf37f08452494212beced973ff6c1d222 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 16 Oct 2024 06:09:07 -0700 Subject: [PATCH 3/4] rename config flag --- core/blockchain.go | 6 +++--- triedb/hashdb/database.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 0eb5c3b243..7fc6c62688 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -198,9 +198,9 @@ func (c *CacheConfig) triedbConfig() *triedb.Config { config := &triedb.Config{Preimages: c.Preimages} if c.StateScheme == rawdb.HashScheme || c.StateScheme == "" { config.HashDB = &hashdb.Config{ - CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, - StatsPrefix: trieCleanCacheStatsNamespace, - ReferenceRoot: true, + CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, + StatsPrefix: trieCleanCacheStatsNamespace, + ReferenceRootAtomicallyOnUpdate: true, } } if c.StateScheme == rawdb.PathScheme { diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go index dde3ce9b99..965a5a631d 100644 --- a/triedb/hashdb/database.go +++ b/triedb/hashdb/database.go @@ -97,9 +97,9 @@ type cache interface { // Config contains the settings for database. type Config struct { - CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes - StatsPrefix string // Prefix for cache stats (disabled if empty) - ReferenceRoot bool // Whether to reference the root node on update + CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes + StatsPrefix string // Prefix for cache stats (disabled if empty) + ReferenceRootAtomicallyOnUpdate bool // Whether to reference the root node on update } // Defaults is the default setting for database if it's not specified. @@ -181,7 +181,7 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas resolver: resolver, cleans: cleans, dirties: make(map[common.Hash]*cachedNode), - referenceRoot: config.ReferenceRoot, + referenceRoot: config.ReferenceRootAtomicallyOnUpdate, } } From cbc2234ee41a3db62336dfcb2da87e6b34a035d8 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 17 Oct 2024 09:23:58 -0700 Subject: [PATCH 4/4] Update triedb/hashdb/database.go Signed-off-by: Darioush Jalali --- triedb/hashdb/database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go index 965a5a631d..fc513c0c50 100644 --- a/triedb/hashdb/database.go +++ b/triedb/hashdb/database.go @@ -631,7 +631,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { // Update inserts the dirty nodes in provided nodeset into database and link the // account trie with multiple storage tries if necessary. -// If ReferenceRoot was enabled in the config, it will also add a reference from +// If ReferenceRootAtomicallyOnUpdate was enabled in the config, it will also add a reference from // the root to the metaroot while holding the db's lock. func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { // Ensure the parent state is present and signal a warning if not.