diff --git a/core/blockchain.go b/core/blockchain.go index da4e463da2..b5d658c079 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2123,21 +2123,23 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, } defer interrupt.Store(true) // terminate the prefetch at the end + // Enable trie node prewarming after the Byzantium fork. Before that, state + // computation occurs at transaction boundaries, making prewarming ineffective. + // The read-only state should also be prewarmed to construct a comprehensive + // execution witness. + if bc.chainConfig.IsByzantium(block.Number()) { + sdb = sdb.EnablePrefetch(makeWitness) + } if bc.cfg.NoPrefetch { statedb, err = state.New(parentRoot, sdb) if err != nil { return nil, err } } else { - // Enable trie node prewarming. The read-only state should also - // be prewarmed for constructing a comprehensive execution witness. - sdb = sdb.EnablePrefetch(makeWitness) - - // If prefetching is enabled, run that against the current state to pre-cache - // transactions and probabilistically some of the account/storage trie nodes. - // - // Note: the main processor and prefetcher share the same reader with a local - // cache for mitigating the overhead of state access. + // If transaction prefetching is enabled, run that against the current state + // to pre-cache transactions. Note: the main processor and prefetcher share + // the same reader with a local cache for mitigating the overhead of state + // access. prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot) if err != nil { return nil, err diff --git a/core/state/database.go b/core/state/database.go index a468dbf9ed..57293b19b4 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -234,6 +234,9 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { // Hasher implements Database, returning a hasher associated with the specified // state root. func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) { + if db.TrieDB().IsVerkle() { + return newBinaryHasher(stateRoot, db.triedb, db.prefetch, db.prefetchRead) + } return newMerkleHasher(stateRoot, db.triedb, db.prefetch, db.prefetchRead) } @@ -328,7 +331,7 @@ func (db *CachingDB) Commit(update *stateUpdate) error { log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err) } } - stateSet, err := update.stateSet() + stateSet, err := update.stateSet(!db.TrieDB().IsVerkle()) if err != nil { return err } diff --git a/core/state/database_hasher.go b/core/state/database_hasher.go index 8bc5277a08..5936f24dbf 100644 --- a/core/state/database_hasher.go +++ b/core/state/database_hasher.go @@ -128,7 +128,6 @@ type Prover interface { // returns an empty state root. type noopHasher struct{} -func (n *noopHasher) Close() {} func (n *noopHasher) UpdateAccount([]common.Address, []AccountMut) error { return nil } func (n *noopHasher) UpdateStorage(common.Address, []common.Hash, []common.Hash) error { return nil diff --git a/core/state/database_hasher_binary.go b/core/state/database_hasher_binary.go index 3d3d8587a7..d1b3823fe1 100644 --- a/core/state/database_hasher_binary.go +++ b/core/state/database_hasher_binary.go @@ -18,95 +18,216 @@ package state import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/triedb" ) +// warpBinTrie pairs a BinaryTrie with an optional background prefetcher that +// preloads trie nodes ahead of mutation. +type warpBinTrie struct { + *bintrie.BinaryTrie + prefetcher *prefetcher +} + +// newWrapBinTrie creates a binary trie with the optional prefetcher enabled. +func newWrapBinTrie(root common.Hash, db *triedb.Database, prefetch bool, prefetchRead bool) (*warpBinTrie, error) { + t, err := bintrie.NewBinaryTrie(root, db) + if err != nil { + return nil, err + } + var p *prefetcher + if prefetch { + p = newPrefetcher(t, prefetchRead) + } + return &warpBinTrie{BinaryTrie: t, prefetcher: p}, nil +} + +// term synchronously terminates the prefetcher (no-op if nil or already done). +// After termination the prefetcher reference is nilled so subsequent calls are +// a cheap pointer check. +func (tr *warpBinTrie) term() { + if tr.prefetcher == nil { + return + } + tr.prefetcher.terminate() + tr.prefetcher = nil +} + +// The methods below shadow the embedded bintrie.BinaryTrie so that any direct trie +// access auto-terminates the prefetcher first. This makes data-race freedom +// structural: callers never need to remember to call term() manually. + +func (tr *warpBinTrie) UpdateAccount(address common.Address, acc *types.StateAccount, codeLen int) error { + tr.term() + return tr.BinaryTrie.UpdateAccount(address, acc, codeLen) +} + +func (tr *warpBinTrie) DeleteAccount(address common.Address) error { + tr.term() + return tr.BinaryTrie.DeleteAccount(address) +} + +func (tr *warpBinTrie) UpdateStorage(address common.Address, key, value []byte) error { + tr.term() + return tr.BinaryTrie.UpdateStorage(address, key, value) +} + +func (tr *warpBinTrie) DeleteStorage(address common.Address, key []byte) error { + tr.term() + return tr.BinaryTrie.DeleteStorage(address, key) +} + +func (tr *warpBinTrie) Hash() common.Hash { + tr.term() + return tr.BinaryTrie.Hash() +} + +func (tr *warpBinTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { + tr.term() + return tr.BinaryTrie.Commit(collectLeaf) +} + +func (tr *warpBinTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + tr.term() + return tr.BinaryTrie.Prove(key, proofDb) +} + +func (tr *warpBinTrie) Witness() map[string][]byte { + tr.term() + return tr.BinaryTrie.Witness() +} + +func (tr *warpBinTrie) prefetchAccounts(addresses []common.Address, read bool) { + if tr.prefetcher == nil { + return + } + tr.prefetcher.scheduleAccounts(addresses, read) +} + +func (tr *warpBinTrie) prefetchStorage(addr common.Address, keys []common.Hash, read bool) { + if tr.prefetcher == nil { + return + } + tr.prefetcher.scheduleSlots(addr, keys, read) +} + +// copy returns a deep-copied state trie. Notably the prefetcher is deliberately +// not copied, as it only belongs to the original one. +func (tr *warpBinTrie) copy() *warpBinTrie { + tr.term() + return &warpBinTrie{BinaryTrie: tr.BinaryTrie.Copy()} +} + // binaryHasher is a Hasher implementation backed by a unified single-layer // binary trie. Accounts, storage slots, and contract code all reside in one // trie, keyed according to the EIP-7864 address space layout. type binaryHasher struct { db *triedb.Database root common.Hash - trie *bintrie.BinaryTrie + + prefetch bool + trie *warpBinTrie } -func newBinaryHasher(root common.Hash, db *triedb.Database) (*binaryHasher, error) { - tr, err := bintrie.NewBinaryTrie(root, db) +func newBinaryHasher(root common.Hash, db *triedb.Database, prefetch bool, prefetchRead bool) (*binaryHasher, error) { + tr, err := newWrapBinTrie(root, db, prefetch, prefetchRead) if err != nil { return nil, err } return &binaryHasher{ - db: db, - root: root, - trie: tr, + db: db, + root: root, + prefetch: prefetch, + trie: tr, }, nil } -func (h *binaryHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error { - for i, addr := range addresses { - acct := accounts[i] +// deleteAccount removes the account specified by the address from the state. +func (h *binaryHasher) deleteAccount(addr common.Address) error { + return h.trie.DeleteAccount(addr) +} - // Deletion: zero out account basic data and code hash so that - // GetAccount returns nil for this address. - if acct.Account == nil { - if err := h.trie.DeleteAccount(addr); err != nil { - return err - } - continue - } - // Determine code size: use the new code length if provided, - // otherwise fall back to the cached or trie-stored value. - // - // TODO(rjl493456442) the contract code length is not assigned - // if it's not modified, fix it. - codeLen := 0 - if acct.Code != nil { - codeLen = len(acct.Code.Code) - } - sa := &types.StateAccount{ - Nonce: acct.Account.Nonce, - Balance: acct.Account.Balance, - CodeHash: acct.Account.CodeHash, - } - if err := h.trie.UpdateAccount(addr, sa, codeLen); err != nil { +// update writes the account specified by the address into the state. +func (h *binaryHasher) updateAccount(addr common.Address, account AccountMut) error { + // Determine code size: use the new code length if provided, + // otherwise fall back to the cached or trie-stored value. + // + // TODO(rjl493456442) the contract code length is not assigned + // if it's not modified, fix it. + codeLen := 0 + if account.Code != nil { + codeLen = len(account.Code.Code) + } + data := &types.StateAccount{ + Nonce: account.Account.Nonce, + Balance: account.Account.Balance, + CodeHash: account.Account.CodeHash, + } + if err := h.trie.UpdateAccount(addr, data, codeLen); err != nil { + return err + } + // Write chunked code into the trie when dirty. + if account.Code != nil && len(account.Code.Code) > 0 { + codeHash := common.BytesToHash(account.Account.CodeHash) + if err := h.trie.UpdateContractCode(addr, codeHash, account.Code.Code); err != nil { return err } - // Write chunked code into the trie when dirty. - if acct.Code != nil && len(acct.Code.Code) > 0 { - codeHash := common.BytesToHash(acct.Account.CodeHash) - if err := h.trie.UpdateContractCode(addr, codeHash, acct.Code.Code); err != nil { - return err - } + } + return nil +} + +// UpdateAccount implements Hasher, writing a list of account mutations +// into the state. The assumption is held all the storage changes have +// already been written beforehand. +func (h *binaryHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error { + var err error + for i, addr := range addresses { + if accounts[i].Account == nil { + err = h.deleteAccount(addr) + } else { + err = h.updateAccount(addr, accounts[i]) + } + if err != nil { + return err } } return nil } +// UpdateStorage implements Hasher, writing a list of storage slot mutations +// into the state. This function must be invoked first before writing the +// associated account metadata into the state. func (h *binaryHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error { + var err error for i, key := range keys { if values[i] == (common.Hash{}) { - if err := h.trie.DeleteStorage(address, key[:]); err != nil { - return err - } + err = h.trie.DeleteStorage(address, key[:]) } else { - if err := h.trie.UpdateStorage(address, key[:], values[i][:]); err != nil { - return err - } + err = h.trie.UpdateStorage(address, key[:], values[i][:]) + } + if err != nil { + return err } } return nil } +// Hash implements Hasher, computing the state root hash without committing. func (h *binaryHasher) Hash() common.Hash { return h.trie.Hash() } +// Commit implements Hasher, finalizing all pending changes and returning +// the resulting state root hash, along with the set of dirty trie nodes +// generated by the updates. func (h *binaryHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) { - root, set := h.trie.Commit(false) nodes := trienode.NewMergedNodeSet() + root, set := h.trie.Commit(false) if set != nil { if err := nodes.Merge(set); err != nil { return common.Hash{}, nil, nil, err @@ -117,12 +238,52 @@ func (h *binaryHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[commo return root, nodes, nil, nil } -func (h *binaryHasher) Close() {} +// Close terminates all prefetcher goroutines. Safe to call multiple times. +func (h *binaryHasher) Close() { + h.trie.term() +} +// Copy implements Hasher, returning a deep-copied hasher instance. func (h *binaryHasher) Copy() Hasher { return &binaryHasher{ - db: h.db, - root: h.root, - trie: h.trie.Copy(), + db: h.db, + root: h.root, + prefetch: false, + trie: h.trie.copy(), } } + +// ProveAccount implements Prover, constructing a proof for the given account. +func (h *binaryHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error { + return h.trie.Prove(crypto.Keccak256(addr.Bytes()), proofDb) +} + +// ProveStorage implements Prover, constructing a proof for the given storage +// slot of the specified account. +func (h *binaryHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error { + return h.trie.Prove(crypto.Keccak256(key.Bytes()), proofDb) +} + +// CollectWitness implements WitnessCollector. It aggregates all trie nodes +// accessed (both read and write) across the account trie, all active storage +// tries and deleted storage tries into a single state witness. +func (h *binaryHasher) CollectWitness(witness *stateless.Witness) { + witness.AddState(h.trie.Witness(), common.Hash{}) +} + +// PrefetchAccount implements Prefetcher, preloading the nodes of specific accounts. +func (h *binaryHasher) PrefetchAccount(addresses []common.Address, read bool) { + if !h.prefetch { + return + } + h.trie.prefetchAccounts(addresses, read) +} + +// PrefetchStorage implements Prefetcher. The storage trie is opened eagerly +// so the prefetcher can begin loading nodes in the background. +func (h *binaryHasher) PrefetchStorage(addr common.Address, keys []common.Hash, read bool) { + if !h.prefetch { + return + } + h.trie.prefetchStorage(addr, keys, read) +} diff --git a/core/state/database_hasher_binary_test.go b/core/state/database_hasher_binary_test.go new file mode 100644 index 0000000000..59f61c1b02 --- /dev/null +++ b/core/state/database_hasher_binary_test.go @@ -0,0 +1,268 @@ +// Copyright 2026 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 state + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/triedb" +) + +// newTestBinaryHasher creates a binaryHasher backed by an in-memory path database. +func newTestBinaryHasher(t *testing.T, db *triedb.Database, root common.Hash, cfg hasherTestConfig) *binaryHasher { + t.Helper() + + h, err := newBinaryHasher(root, db, cfg.prefetch, cfg.prefetchRead) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { h.Close() }) + return h +} + +// commitAndReopenBinary commits the hasher's state and reopens a fresh hasher +// from the committed root. This simulates a block boundary. +func commitAndReopenBinary(t *testing.T, h *binaryHasher, cfg hasherTestConfig) *binaryHasher { + t.Helper() + + root, nodes, _, err := h.Commit() + if err != nil { + t.Fatal(err) + } + if nodes != nil { + if err := h.db.Update(root, h.root, 0, nodes, triedb.NewStateSet()); err != nil { + t.Fatal(err) + } + if err := h.db.Commit(root, false); err != nil { + t.Fatal(err) + } + } + h2, err := newBinaryHasher(root, h.db, cfg.prefetch, cfg.prefetchRead) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { h2.Close() }) + return h2 +} + +// makeBinaryBaseState creates a non-empty state as the starting point for tests. +// The base contains: +// - addr1: nonce=1, balance=100, storage={slot1: val1, slot2: val2} +// - addr2: nonce=2, balance=200, no storage +// +// The state is committed and flushed so the hasher returned opens from disk. +func makeBinaryBaseState(t *testing.T, cfg hasherTestConfig) *binaryHasher { + t.Helper() + + noPrefetch := hasherTestConfig{"base", false, false} + db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults) + h := newTestBinaryHasher(t, db, types.EmptyBinaryHash, noPrefetch) + + if err := h.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot1, hasherSlot2}, []common.Hash{hasherVal1, hasherVal2}); err != nil { + t.Fatal(err) + } + if err := h.UpdateAccount( + []common.Address{hasherAddr1, hasherAddr2}, + []AccountMut{hasherAccount(1, 100), hasherAccount(2, 200)}, + ); err != nil { + t.Fatal(err) + } + return commitAndReopenBinary(t, h, cfg) +} + +// TestBinaryHasherBasic verifies that mutating storage and accounts on top of +// a non-empty base state produces a deterministic, non-empty root and that the +// root survives a commit+reopen cycle. +func TestBinaryHasherBasic(t *testing.T) { + for _, cfg := range hasherTestConfigs { + t.Run(cfg.name, func(t *testing.T) { + h := makeBinaryBaseState(t, cfg) + + if cfg.prefetch { + h.PrefetchStorage(hasherAddr1, []common.Hash{hasherSlot3}, false) + h.PrefetchAccount([]common.Address{hasherAddr1, hasherAddr3}, false) + } + if err := h.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot3}, []common.Hash{hasherVal3}); err != nil { + t.Fatal(err) + } + if err := h.UpdateAccount( + []common.Address{hasherAddr1, hasherAddr3}, + []AccountMut{hasherAccount(1, 100), hasherAccount(3, 300)}, + ); err != nil { + t.Fatal(err) + } + root := h.Hash() + if root == types.EmptyRootHash { + t.Fatal("expected non-empty root after mutations") + } + h2 := commitAndReopenBinary(t, h, cfg) + if h2.Hash() != root { + t.Fatalf("root mismatch after reopen: got %x, want %x", h2.Hash(), root) + } + }) + } +} + +// TestBinaryHasherPrefetchReadOnly verifies that read-only prefetching (for +// accounts and storage that are never subsequently mutated) does not corrupt +// state. Both prefetchRead=true (requests are processed) and prefetchRead=false +// (requests are dropped) are tested. +func TestBinaryHasherPrefetchReadOnly(t *testing.T) { + for _, prefetchRead := range []bool{false, true} { + name := "readDropped" + if prefetchRead { + name = "readProcessed" + } + t.Run(name, func(t *testing.T) { + cfg := hasherTestConfig{name, true, prefetchRead} + h := makeBinaryBaseState(t, cfg) + rootBefore := h.Hash() + + // Prefetch addr1's account and storage (read-only). + h.PrefetchAccount([]common.Address{hasherAddr1, hasherAddr2}, true) + h.PrefetchStorage(hasherAddr1, []common.Hash{hasherSlot1, hasherSlot2}, true) + + // Only mutate addr2 — addr1's prefetched data is never written. + if err := h.UpdateAccount( + []common.Address{hasherAddr2}, + []AccountMut{hasherAccount(2, 300)}, + ); err != nil { + t.Fatal(err) + } + root := h.Hash() + if root == rootBefore { + t.Fatal("expected root to change after balance update") + } + h2 := commitAndReopenBinary(t, h, hasherTestConfig{"verify", false, false}) + if h2.Hash() != root { + t.Fatalf("root mismatch: got %x, want %x", h2.Hash(), root) + } + }) + } +} + +// TestBinaryHasherPrefetchDeterminism verifies that the resulting root is +// identical across all prefetch configurations for the same set of mutations. +func TestBinaryHasherPrefetchDeterminism(t *testing.T) { + var roots []common.Hash + for _, cfg := range hasherTestConfigs { + h := makeBinaryBaseState(t, cfg) + + if cfg.prefetch { + h.PrefetchAccount([]common.Address{hasherAddr1, hasherAddr3}, false) + h.PrefetchStorage(hasherAddr1, []common.Hash{hasherSlot3}, false) + h.PrefetchStorage(hasherAddr3, []common.Hash{hasherSlot1}, false) + } + if err := h.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot3}, []common.Hash{hasherVal3}); err != nil { + t.Fatal(err) + } + if err := h.UpdateStorage(hasherAddr3, []common.Hash{hasherSlot1}, []common.Hash{hasherVal1}); err != nil { + t.Fatal(err) + } + if err := h.UpdateAccount( + []common.Address{hasherAddr1, hasherAddr3}, + []AccountMut{hasherAccount(1, 100), hasherAccount(3, 300)}, + ); err != nil { + t.Fatal(err) + } + roots = append(roots, h.Hash()) + } + for i := 1; i < len(roots); i++ { + if roots[i] != roots[0] { + t.Fatalf("root diverged: config[0]=%x config[%d]=%x", roots[0], i, roots[i]) + } + } +} + +// TestBinaryHasherCopy verifies that Copy produces an independent snapshot: +// mutations on the copy must not affect the original's hash. +func TestBinaryHasherCopy(t *testing.T) { + cfg := hasherTestConfig{"prefetchAll", true, true} + h := makeBinaryBaseState(t, cfg) + + h.PrefetchAccount([]common.Address{hasherAddr1}, false) + h.PrefetchStorage(hasherAddr1, []common.Hash{hasherSlot3}, false) + if err := h.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot3}, []common.Hash{hasherVal3}); err != nil { + t.Fatal(err) + } + if err := h.UpdateAccount([]common.Address{hasherAddr1}, []AccountMut{hasherAccount(1, 100)}); err != nil { + t.Fatal(err) + } + origRoot := h.Hash() + + cpy := h.Copy() + defer cpy.(*binaryHasher).Close() + + // Mutate the copy: delete slot3, add slot2 with new value. + if err := cpy.UpdateStorage(hasherAddr1, []common.Hash{hasherSlot3, hasherSlot2}, []common.Hash{{}, hasherVal3}); err != nil { + t.Fatal(err) + } + if err := cpy.UpdateAccount([]common.Address{hasherAddr1}, []AccountMut{hasherAccount(1, 100)}); err != nil { + t.Fatal(err) + } + if cpy.Hash() == origRoot { + t.Fatal("copy should diverge after mutation") + } + if h.Hash() != origRoot { + t.Fatal("original root changed after mutating copy") + } +} + +// TestBinaryHasherWitness verifies that the witness returned by CollectWitness +// contains trie nodes for accessed accounts and storage. When read-only +// prefetching is enabled, the prefetched (but never written) data must also +// appear in the witness. +func TestBinaryHasherWitness(t *testing.T) { + // Collect witness WITHOUT read-prefetching: only mutated paths are tracked. + collectWitness := func(prefetchRead bool) int { + cfg := hasherTestConfig{"witness", true, prefetchRead} + h := makeBinaryBaseState(t, cfg) + + // Read-only prefetch of addr1 account and slot1 (never mutated below). + h.PrefetchAccount([]common.Address{hasherAddr1}, true) + h.PrefetchStorage(hasherAddr1, []common.Hash{hasherSlot1}, true) + + // Mutate only addr2 (no storage). + if err := h.UpdateAccount( + []common.Address{hasherAddr2}, + []AccountMut{hasherAccount(2, 300)}, + ); err != nil { + t.Fatal(err) + } + h.Hash() + + witness := &stateless.Witness{ + Codes: make(map[string]struct{}), + State: make(map[string]struct{}), + } + h.CollectWitness(witness) + return len(witness.State) + } + nodesWithoutRead := collectWitness(false) + nodesWithRead := collectWitness(true) + + if nodesWithoutRead == 0 { + t.Fatal("witness should contain trie nodes even without read prefetching") + } + if nodesWithRead <= nodesWithoutRead { + t.Fatalf("read-only prefetching should add extra nodes to witness: got %d (with read) vs %d (without)", nodesWithRead, nodesWithoutRead) + } +} diff --git a/core/state/database_hasher_merkle.go b/core/state/database_hasher_merkle.go index 237f7f1f07..1d131ef4af 100644 --- a/core/state/database_hasher_merkle.go +++ b/core/state/database_hasher_merkle.go @@ -66,9 +66,9 @@ func (tr *wrapTrie) term() { // access auto-terminates the prefetcher first. This makes data-race freedom // structural: callers never need to remember to call term() manually. -func (tr *wrapTrie) UpdateAccount(address common.Address, acc *types.StateAccount, codeLen int) error { +func (tr *wrapTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error { tr.term() - return tr.StateTrie.UpdateAccount(address, acc, codeLen) + return tr.StateTrie.UpdateAccount(address, acc, 0) } func (tr *wrapTrie) DeleteAccount(address common.Address) error { @@ -106,6 +106,7 @@ func (tr *wrapTrie) Witness() map[string][]byte { return tr.StateTrie.Witness() } +// prefetchAccounts prewarms the trie with the specified account list. func (tr *wrapTrie) prefetchAccounts(addresses []common.Address, read bool) { if tr.prefetcher == nil { return @@ -113,6 +114,7 @@ func (tr *wrapTrie) prefetchAccounts(addresses []common.Address, read bool) { tr.prefetcher.scheduleAccounts(addresses, read) } +// prefetchStorage prewarms the trie with the specified storage list. func (tr *wrapTrie) prefetchStorage(addr common.Address, keys []common.Hash, read bool) { if tr.prefetcher == nil { return @@ -281,7 +283,7 @@ func (h *merkleHasher) updateAccount(addr common.Address, account AccountMut) er Root: root, CodeHash: account.Account.CodeHash, } - return h.acctTrie.UpdateAccount(addr, data, 0) + return h.acctTrie.UpdateAccount(addr, data) } // UpdateAccount implements Hasher, writing a list of account mutations diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go index 453fc7fe5d..7482fc2d84 100644 --- a/core/state/statedb_fuzz_test.go +++ b/core/state/statedb_fuzz_test.go @@ -183,7 +183,7 @@ func (test *stateTest) run() bool { storages []map[common.Hash]map[common.Hash][]byte storageOrigin []map[common.Address]map[common.Hash][]byte copyUpdate = func(update *stateUpdate) { - encoded, _ := update.stateSet() + encoded, _ := update.stateSet(true) accounts = append(accounts, maps.Clone(encoded.Accounts)) accountOrigin = append(accountOrigin, maps.Clone(encoded.AccountsOrigin)) storages = append(storages, maps.Clone(encoded.Storages)) diff --git a/core/state/stateupdate.go b/core/state/stateupdate.go index 8a2bb34065..ca6d5382da 100644 --- a/core/state/stateupdate.go +++ b/core/state/stateupdate.go @@ -250,12 +250,70 @@ func (sc *stateUpdate) encodeMerkle() (map[common.Hash][]byte, map[common.Addres return accounts, accountOrigin, storages, storageOrigin, nil } +func (sc *stateUpdate) encodeBinary() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte, error) { + var ( + accounts = make(map[common.Hash][]byte) + storages = make(map[common.Hash]map[common.Hash][]byte) + accountOrigin = make(map[common.Address][]byte) + storageOrigin = make(map[common.Address]map[common.Hash][]byte) + ) + for addr, prev := range sc.accountsOrigin { + if prev == nil { + accountOrigin[addr] = nil + } else { + accountOrigin[addr] = types.SlimAccountRLP(types.StateAccount{ + Balance: prev.Balance, + Nonce: prev.Nonce, + CodeHash: prev.CodeHash, + }) + } + + addrHash := crypto.Keccak256Hash(addr.Bytes()) + data := sc.accounts[addrHash] + if data == nil { + accounts[addrHash] = nil + } else { + accounts[addrHash] = types.SlimAccountRLP(types.StateAccount{ + Balance: data.Balance, + Nonce: data.Nonce, + CodeHash: data.CodeHash, + }) + } + } + for addr, slots := range sc.storagesOrigin { + subset := make(map[common.Hash][]byte) + for key, val := range slots { + subset[key] = encodeSlot(val) + } + storageOrigin[addr] = subset + } + for addrHash, slots := range sc.storages { + subset := make(map[common.Hash][]byte) + for key, val := range slots { + subset[key] = encodeSlot(val) + } + storages[addrHash] = subset + } + return accounts, accountOrigin, storages, storageOrigin, nil +} + // stateSet converts the current stateUpdate object into a triedb.StateSet // object. This function extracts the necessary data from the stateUpdate // struct and formats it into the StateSet structure consumed by the triedb // package. -func (sc *stateUpdate) stateSet() (*triedb.StateSet, error) { - accounts, accountOrigin, storages, storageOrigin, err := sc.encodeMerkle() +func (sc *stateUpdate) stateSet(isMerkle bool) (*triedb.StateSet, error) { + var ( + err error + accounts = make(map[common.Hash][]byte) + storages = make(map[common.Hash]map[common.Hash][]byte) + accountOrigin = make(map[common.Address][]byte) + storageOrigin = make(map[common.Address]map[common.Hash][]byte) + ) + if isMerkle { + accounts, accountOrigin, storages, storageOrigin, err = sc.encodeMerkle() + } else { + accounts, accountOrigin, storages, storageOrigin, err = sc.encodeBinary() + } if err != nil { return nil, err }