diff --git a/core/state/database_hasher.go b/core/state/database_hasher.go index 2b8d407139..e07f666f07 100644 --- a/core/state/database_hasher.go +++ b/core/state/database_hasher.go @@ -17,17 +17,10 @@ package state import ( - "maps" - "sync" - "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" "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-ethereum/triedb" ) // CodeMut represents a mutation to contract code. @@ -144,194 +137,3 @@ func (n *noopHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common. return common.Hash{}, trienode.NewMergedNodeSet(), make(map[common.Address]Hashes), nil } func (n *noopHasher) Copy() Hasher { return &noopHasher{} } - -// merkleHasher is a Hasher implementation backed by the traditional two-layer -// Merkle Patricia Trie (separate account trie and per-account storage tries). -type merkleHasher struct { - db *triedb.Database - root common.Hash - - accountTrie *trie.StateTrie - storageTries map[common.Address]*trie.StateTrie // lazily opened - - // storageRoots tracks the storage root transition for every mutated - // account. Prev is recorded once (first touch) and Hash is updated - // on each UpdateAccount call. - storageRoots map[common.Address]Hashes - - lock sync.Mutex // guards storageTries (concurrent updateTrie) -} - -func newMerkleHasher(root common.Hash, db *triedb.Database) (*merkleHasher, error) { - tr, err := trie.NewStateTrie(trie.StateTrieID(root), db) - if err != nil { - return nil, err - } - return &merkleHasher{ - db: db, - root: root, - accountTrie: tr, - storageTries: make(map[common.Address]*trie.StateTrie), - storageRoots: make(map[common.Address]Hashes), - }, nil -} - -// accountStorageRoot reads the storage root of account from the account trie. -func (h *merkleHasher) accountStorageRoot(addr common.Address) common.Hash { - if acc, _ := h.accountTrie.GetAccount(addr); acc != nil { - return acc.Root - } - return types.EmptyRootHash -} - -// recordOrigin records the original (pre-mutation) storage root for addr. -// Only the first call per address has any effect. -func (h *merkleHasher) recordOrigin(addr common.Address) { - if _, ok := h.storageRoots[addr]; !ok { - root := h.accountStorageRoot(addr) - h.storageRoots[addr] = Hashes{ - Prev: root, - Hash: root, - } - } -} - -// openStorageTrie returns the cached storage trie for the given address, -// or opens one from the database if not already cached. -func (h *merkleHasher) openStorageTrie(address common.Address) (*trie.StateTrie, error) { - if st, ok := h.storageTries[address]; ok { - return st, nil - } - // Record the original storage trie root if it has not already been tracked - // when the storage trie is loaded. - h.recordOrigin(address) - - id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), h.accountStorageRoot(address)) - st, err := trie.NewStateTrie(id, h.db) - if err != nil { - return nil, err - } - h.storageTries[address] = st - return st, nil -} - -func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error { - h.lock.Lock() - st, err := h.openStorageTrie(address) - if err != nil { - h.lock.Unlock() - return err - } - h.lock.Unlock() - - for i, key := range keys { - if values[i] == (common.Hash{}) { - if err := st.DeleteStorage(address, key[:]); err != nil { - return err - } - } else { - if err := st.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:])); err != nil { - return err - } - } - } - return nil -} - -func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error { - for i, addr := range addresses { - h.recordOrigin(addr) - acct := accounts[i] - - // Deletion: remove from account trie and evict any cached - // storage trie so a re-created account starts fresh. - if acct.Account == nil { - if err := h.accountTrie.DeleteAccount(addr); err != nil { - return err - } - delete(h.storageTries, addr) - - h.storageRoots[addr] = Hashes{ - Prev: h.storageRoots[addr].Prev, - Hash: types.EmptyRootHash, - } - continue - } - // Determine storage root from the cached trie (if storage was - // modified) or from the account trie (unchanged storage). - storageRoot := h.accountStorageRoot(addr) - if st, ok := h.storageTries[addr]; ok { - storageRoot = st.Hash() - } - sa := &types.StateAccount{ - Nonce: acct.Account.Nonce, - Balance: acct.Account.Balance, - Root: storageRoot, - CodeHash: acct.Account.CodeHash, - } - if err := h.accountTrie.UpdateAccount(addr, sa, 0); err != nil { - return err - } - h.storageRoots[addr] = Hashes{ - Prev: h.storageRoots[addr].Prev, - Hash: storageRoot, - } - } - return nil -} - -func (h *merkleHasher) Hash() common.Hash { - return h.accountTrie.Hash() -} - -func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) { - nodes := trienode.NewMergedNodeSet() - - // Commit all dirty storage tries. - for _, st := range h.storageTries { - if _, set := st.Commit(false); set != nil { - if err := nodes.Merge(set); err != nil { - return common.Hash{}, nil, nil, err - } - } - } - // Commit the account trie. collectLeaf must be true so that hashdb - // can link account trie leaves to their storage trie roots. - root, set := h.accountTrie.Commit(true) - if set != nil { - if err := nodes.Merge(set); err != nil { - return common.Hash{}, nil, nil, err - } - } - return root, nodes, h.storageRoots, nil -} - -func (h *merkleHasher) Copy() Hasher { - cpy := &merkleHasher{ - db: h.db, - root: h.root, - accountTrie: h.accountTrie.Copy(), - storageTries: make(map[common.Address]*trie.StateTrie, len(h.storageTries)), - storageRoots: maps.Clone(h.storageRoots), - } - for addr, st := range h.storageTries { - cpy.storageTries[addr] = st.Copy() - } - return cpy -} - -// ProveAccount implements Prover by constructing a Merkle proof for the -// given account against the current account trie. -func (h *merkleHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error { - return h.accountTrie.Prove(crypto.Keccak256(addr.Bytes()), proofDb) -} - -// ProveStorage implements Prover by constructing a Merkle proof for the given -// storage slot. The storage trie is opened lazily if not already cached. -func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error { - st, err := h.openStorageTrie(addr) - if err != nil { - return err - } - return st.Prove(crypto.Keccak256(key.Bytes()), proofDb) -} diff --git a/core/state/database_hasher_binary.go b/core/state/database_hasher_binary.go new file mode 100644 index 0000000000..6d983770e8 --- /dev/null +++ b/core/state/database_hasher_binary.go @@ -0,0 +1,126 @@ +// 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 ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie/bintrie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb" +) + +// 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 +} + +func newBinaryHasher(root common.Hash, db *triedb.Database) (*binaryHasher, error) { + tr, err := bintrie.NewBinaryTrie(root, db) + if err != nil { + return nil, err + } + return &binaryHasher{ + db: db, + root: root, + trie: tr, + }, nil +} + +func (h *binaryHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error { + for i, addr := range addresses { + acct := accounts[i] + + // 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 { + 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 +} + +func (h *binaryHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error { + for i, key := range keys { + if values[i] == (common.Hash{}) { + if err := h.trie.DeleteStorage(address, key[:]); err != nil { + return err + } + } else { + if err := h.trie.UpdateStorage(address, key[:], values[i][:]); err != nil { + return err + } + } + } + return nil +} + +func (h *binaryHasher) Hash() common.Hash { + return h.trie.Hash() +} + +func (h *binaryHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) { + root, set := h.trie.Commit(false) + nodes := trienode.NewMergedNodeSet() + if set != nil { + if err := nodes.Merge(set); err != nil { + return common.Hash{}, nil, nil, err + } + } + // The binary trie is a single unified structure with no per-account + // storage sub-tries, so there are no secondary hashes to report. + return root, nodes, nil, nil +} + +func (h *binaryHasher) Copy() Hasher { + return &binaryHasher{ + db: h.db, + root: h.root, + trie: h.trie.Copy(), + } +} diff --git a/core/state/database_hasher_merkle.go b/core/state/database_hasher_merkle.go new file mode 100644 index 0000000000..48d3d48855 --- /dev/null +++ b/core/state/database_hasher_merkle.go @@ -0,0 +1,221 @@ +// 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 ( + "maps" + "sync" + + "github.com/ethereum/go-ethereum/common" + "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" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb" +) + +// merkleHasher is a Hasher implementation backed by the traditional two-layer +// Merkle Patricia Trie (separate account trie and per-account storage tries). +type merkleHasher struct { + db *triedb.Database + root common.Hash + + accountTrie *trie.StateTrie + storageTries map[common.Address]*trie.StateTrie // lazily opened + + // storageRoots tracks the storage root transition for every mutated + // account. Prev is recorded once (first touch) and Hash is updated + // on each UpdateAccount call. + storageRoots map[common.Address]Hashes + + lock sync.Mutex // guards storageTries (concurrent updateTrie) +} + +func newMerkleHasher(root common.Hash, db *triedb.Database) (*merkleHasher, error) { + tr, err := trie.NewStateTrie(trie.StateTrieID(root), db) + if err != nil { + return nil, err + } + return &merkleHasher{ + db: db, + root: root, + accountTrie: tr, + storageTries: make(map[common.Address]*trie.StateTrie), + storageRoots: make(map[common.Address]Hashes), + }, nil +} + +// accountStorageRoot reads the storage root of account from the account trie. +func (h *merkleHasher) accountStorageRoot(addr common.Address) common.Hash { + if acc, _ := h.accountTrie.GetAccount(addr); acc != nil { + return acc.Root + } + return types.EmptyRootHash +} + +// recordOrigin records the original (pre-mutation) storage root for addr. +// Only the first call per address has any effect. +func (h *merkleHasher) recordOrigin(addr common.Address) { + if _, ok := h.storageRoots[addr]; !ok { + root := h.accountStorageRoot(addr) + h.storageRoots[addr] = Hashes{ + Prev: root, + Hash: root, + } + } +} + +// openStorageTrie returns the cached storage trie for the given address, +// or opens one from the database if not already cached. +func (h *merkleHasher) openStorageTrie(address common.Address) (*trie.StateTrie, error) { + if st, ok := h.storageTries[address]; ok { + return st, nil + } + // Record the original storage trie root if it has not already been tracked + // when the storage trie is loaded. + h.recordOrigin(address) + + id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), h.accountStorageRoot(address)) + st, err := trie.NewStateTrie(id, h.db) + if err != nil { + return nil, err + } + h.storageTries[address] = st + return st, nil +} + +func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error { + h.lock.Lock() + st, err := h.openStorageTrie(address) + if err != nil { + h.lock.Unlock() + return err + } + h.lock.Unlock() + + for i, key := range keys { + if values[i] == (common.Hash{}) { + if err := st.DeleteStorage(address, key[:]); err != nil { + return err + } + } else { + if err := st.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:])); err != nil { + return err + } + } + } + return nil +} + +func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error { + for i, addr := range addresses { + h.recordOrigin(addr) + acct := accounts[i] + + // Deletion: remove from account trie and evict any cached + // storage trie so a re-created account starts fresh. + if acct.Account == nil { + if err := h.accountTrie.DeleteAccount(addr); err != nil { + return err + } + delete(h.storageTries, addr) + + h.storageRoots[addr] = Hashes{ + Prev: h.storageRoots[addr].Prev, + Hash: types.EmptyRootHash, + } + continue + } + // Determine storage root from the cached trie (if storage was + // modified) or from the account trie (unchanged storage). + storageRoot := h.accountStorageRoot(addr) + if st, ok := h.storageTries[addr]; ok { + storageRoot = st.Hash() + } + sa := &types.StateAccount{ + Nonce: acct.Account.Nonce, + Balance: acct.Account.Balance, + Root: storageRoot, + CodeHash: acct.Account.CodeHash, + } + if err := h.accountTrie.UpdateAccount(addr, sa, 0); err != nil { + return err + } + h.storageRoots[addr] = Hashes{ + Prev: h.storageRoots[addr].Prev, + Hash: storageRoot, + } + } + return nil +} + +func (h *merkleHasher) Hash() common.Hash { + return h.accountTrie.Hash() +} + +func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) { + nodes := trienode.NewMergedNodeSet() + + // Commit all dirty storage tries. + for _, st := range h.storageTries { + if _, set := st.Commit(false); set != nil { + if err := nodes.Merge(set); err != nil { + return common.Hash{}, nil, nil, err + } + } + } + // Commit the account trie. collectLeaf must be true so that hashdb + // can link account trie leaves to their storage trie roots. + root, set := h.accountTrie.Commit(true) + if set != nil { + if err := nodes.Merge(set); err != nil { + return common.Hash{}, nil, nil, err + } + } + return root, nodes, h.storageRoots, nil +} + +func (h *merkleHasher) Copy() Hasher { + cpy := &merkleHasher{ + db: h.db, + root: h.root, + accountTrie: h.accountTrie.Copy(), + storageTries: make(map[common.Address]*trie.StateTrie, len(h.storageTries)), + storageRoots: maps.Clone(h.storageRoots), + } + for addr, st := range h.storageTries { + cpy.storageTries[addr] = st.Copy() + } + return cpy +} + +// ProveAccount implements Prover by constructing a Merkle proof for the +// given account against the current account trie. +func (h *merkleHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error { + return h.accountTrie.Prove(crypto.Keccak256(addr.Bytes()), proofDb) +} + +// ProveStorage implements Prover by constructing a Merkle proof for the given +// storage slot. The storage trie is opened lazily if not already cached. +func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error { + st, err := h.openStorageTrie(addr) + if err != nil { + return err + } + return st.Prove(crypto.Keccak256(key.Bytes()), proofDb) +}