From 2656abc317d78a7c8f2f902cd93b002a0826d154 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Thu, 7 Aug 2025 16:35:58 +0200
Subject: [PATCH 01/10] trie{,/utils}: add tests for eip7864 binary trees
Signed-off-by: Guillaume Ballet
<3272758+gballet@users.noreply.github.com>
---
go.mod | 3 +-
go.sum | 10 +-
trie/binary.go | 319 ++++++++++++++++++++++++++++++++++
trie/binary_iterator.go | 236 +++++++++++++++++++++++++
trie/binary_iterator_test.go | 69 ++++++++
trie/binary_test.go | 198 +++++++++++++++++++++
trie/bintrie/binary_node.go | 121 +++++++++++++
trie/bintrie/empty.go | 71 ++++++++
trie/bintrie/hashed_node.go | 66 +++++++
trie/bintrie/internal_node.go | 194 +++++++++++++++++++++
trie/bintrie/stem_node.go | 216 +++++++++++++++++++++++
trie/utils/binary.go | 68 ++++++++
12 files changed, 1568 insertions(+), 3 deletions(-)
create mode 100644 trie/binary.go
create mode 100644 trie/binary_iterator.go
create mode 100644 trie/binary_iterator_test.go
create mode 100644 trie/binary_test.go
create mode 100644 trie/bintrie/binary_node.go
create mode 100644 trie/bintrie/empty.go
create mode 100644 trie/bintrie/hashed_node.go
create mode 100644 trie/bintrie/internal_node.go
create mode 100644 trie/bintrie/stem_node.go
create mode 100644 trie/utils/binary.go
diff --git a/go.mod b/go.mod
index 363d7d3dfb0..ffde28f005b 100644
--- a/go.mod
+++ b/go.mod
@@ -62,6 +62,7 @@ require (
github.com/supranational/blst v0.3.14
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/urfave/cli/v2 v2.27.5
+ github.com/zeebo/blake3 v0.2.4
go.uber.org/automaxprocs v1.5.2
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.36.0
@@ -115,7 +116,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
- github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
diff --git a/go.sum b/go.sum
index 099d432ba40..4a3aac1c129 100644
--- a/go.sum
+++ b/go.sum
@@ -220,8 +220,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-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/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
+github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -365,6 +365,12 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
+github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
+github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
+github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
+github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
diff --git a/trie/binary.go b/trie/binary.go
new file mode 100644
index 00000000000..2cad0999c7f
--- /dev/null
+++ b/trie/binary.go
@@ -0,0 +1,319 @@
+// Copyright 2025 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 trie
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "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/trie/utils"
+ "github.com/ethereum/go-ethereum/triedb/database"
+ "github.com/holiman/uint256"
+)
+
+// zero is the zero value for a 32-byte array.
+var zero [32]byte
+
+// NewBinaryNode creates a new empty binary trie
+func NewBinaryNode() bintrie.BinaryNode {
+ return bintrie.Empty{}
+}
+
+// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
+// interface so that Verkle trees can be reused verbatim.
+type BinaryTrie struct {
+ root bintrie.BinaryNode
+ reader *trieReader
+}
+
+// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
+func (trie *BinaryTrie) ToDot() string {
+ trie.root.Hash()
+ return bintrie.ToDot(trie.root)
+}
+
+// NewBinaryTrie creates a new binary trie.
+func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) {
+ reader, err := newTrieReader(root, common.Hash{}, db)
+ if err != nil {
+ return nil, err
+ }
+ // Parse the root verkle node if it's not empty.
+ node := NewBinaryNode()
+ if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
+ blob, err := reader.node(nil, common.Hash{})
+ if err != nil {
+ return nil, err
+ }
+ node, err = bintrie.DeserializeNode(blob, 0)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &BinaryTrie{
+ root: node,
+ reader: reader,
+ }, nil
+}
+
+// FlatdbNodeResolver is a node resolver that reads nodes from the flatdb.
+func (trie *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
+ // empty nodes will be serialized as common.Hash{}, so capture
+ // this special use case.
+ if hash == (common.Hash{}) {
+ return nil, nil // empty node
+ }
+ return trie.reader.node(path, hash)
+}
+
+// GetKey returns the sha3 preimage of a hashed key that was previously used
+// to store a value.
+func (trie *BinaryTrie) GetKey(key []byte) []byte {
+ return key
+}
+
+// Get returns the value for key stored in the trie. The value bytes must
+// not be modified by the caller. If a node was not found in the database, a
+// trie.MissingNodeError is returned.
+func (trie *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
+ return trie.root.Get(utils.GetBinaryTreeKey(addr, key), trie.FlatdbNodeResolver)
+}
+
+// GetWithHashedKey returns the value, assuming that the key has already
+// been hashed.
+func (trie *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
+ return trie.root.Get(key, trie.FlatdbNodeResolver)
+}
+
+// GetAccount returns the account information for the given address.
+func (trie *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
+ acc := &types.StateAccount{}
+ versionkey := utils.GetBinaryTreeKey(addr, zero[:])
+ var (
+ values [][]byte
+ err error
+ )
+ switch r := trie.root.(type) {
+ case *bintrie.InternalNode:
+ values, err = r.GetValuesAtStem(versionkey[:31], trie.FlatdbNodeResolver)
+ case *bintrie.StemNode:
+ values = r.Values
+ case bintrie.Empty:
+ return nil, nil
+ default:
+ // This will cover HashedNode but that should be fine since the
+ // root node should always be resolved.
+ return nil, errInvalidRootType
+ }
+ if err != nil {
+ return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
+ }
+
+ // The following code is required for the MPT->VKT conversion.
+ // An account can be partially migrated, where storage slots were moved to the VKT
+ // but not yet the account. This means some account information as (header) storage slots
+ // are in the VKT but basic account information must be read in the base tree (MPT).
+ // TODO: we can simplify this logic depending if the conversion is in progress or finished.
+ emptyAccount := true
+
+ for i := 0; values != nil && i <= utils.CodeHashLeafKey && emptyAccount; i++ {
+ emptyAccount = emptyAccount && values[i] == nil
+ }
+ if emptyAccount {
+ return nil, nil
+ }
+
+ // if the account has been deleted, then values[10] will be 0 and not nil. If it has
+ // been recreated after that, then its code keccak will NOT be 0. So return `nil` if
+ // the nonce, and values[10], and code keccak is 0.
+ if bytes.Equal(values[utils.BasicDataLeafKey], zero[:]) && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeHashLeafKey], zero[:]) {
+ return nil, nil
+ }
+
+ acc.Nonce = binary.BigEndian.Uint64(values[utils.BasicDataLeafKey][utils.BasicDataNonceOffset:])
+ var balance [16]byte
+ copy(balance[:], values[utils.BasicDataLeafKey][utils.BasicDataBalanceOffset:])
+ acc.Balance = new(uint256.Int).SetBytes(balance[:])
+ acc.CodeHash = values[utils.CodeHashLeafKey]
+
+ return acc, nil
+}
+
+// UpdateAccount updates the account information for the given address.
+func (trie *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
+ var (
+ err error
+ basicData [32]byte
+ values = make([][]byte, bintrie.NodeWidth)
+ stem = utils.GetBinaryTreeKey(addr, zero[:])
+ )
+
+ binary.BigEndian.PutUint32(basicData[utils.BasicDataCodeSizeOffset-1:], uint32(codeLen))
+ binary.BigEndian.PutUint64(basicData[utils.BasicDataNonceOffset:], acc.Nonce)
+ // Because the balance is a max of 16 bytes, truncate
+ // the extra values. This happens in devmode, where
+ // 0xff**32 is allocated to the developer account.
+ balanceBytes := acc.Balance.Bytes()
+ // TODO: reduce the size of the allocation in devmode, then panic instead
+ // of truncating.
+ if len(balanceBytes) > 16 {
+ balanceBytes = balanceBytes[16:]
+ }
+ copy(basicData[32-len(balanceBytes):], balanceBytes[:])
+ values[utils.BasicDataLeafKey] = basicData[:]
+ values[utils.CodeHashLeafKey] = acc.CodeHash[:]
+
+ trie.root, err = trie.root.InsertValuesAtStem(stem, values, trie.FlatdbNodeResolver, 0)
+ return err
+}
+
+// UpdateStem updates the values for the given stem key.
+func (trie *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
+ var err error
+ trie.root, err = trie.root.InsertValuesAtStem(key, values, trie.FlatdbNodeResolver, 0)
+ return err
+}
+
+// Update associates key with value in the trie. If value has length zero, any
+// existing value is deleted from the trie. The value bytes must not be modified
+// by the caller while they are stored in the trie. If a node was not found in the
+// database, a trie.MissingNodeError is returned.
+func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
+ k := utils.GetBinaryTreeKeyStorageSlot(address, key)
+ var v [32]byte
+ if len(value) >= 32 {
+ copy(v[:], value[:32])
+ } else {
+ copy(v[32-len(value):], value[:])
+ }
+ root, err := trie.root.Insert(k, v[:], trie.FlatdbNodeResolver)
+ if err != nil {
+ return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
+ }
+ trie.root = root
+ return nil
+}
+
+// DeleteAccount is a no-op as it is disabled in stateless.
+func (trie *BinaryTrie) DeleteAccount(addr common.Address) error {
+ return nil
+}
+
+// Delete removes any existing value for key from the trie. If a node was not
+// found in the database, a trie.MissingNodeError is returned.
+func (trie *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
+ k := utils.GetBinaryTreeKey(addr, key)
+ var zero [32]byte
+ root, err := trie.root.Insert(k, zero[:], trie.FlatdbNodeResolver)
+ if err != nil {
+ return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
+ }
+ trie.root = root
+ return nil
+}
+
+// Hash returns the root hash of the trie. It does not write to the database and
+// can be used even if the trie doesn't have one.
+func (trie *BinaryTrie) Hash() common.Hash {
+ return trie.root.Hash()
+}
+
+// Commit writes all nodes to the trie's memory database, tracking the internal
+// and external (for account tries) references.
+func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
+ root := trie.root.(*bintrie.InternalNode)
+ nodeset := trienode.NewNodeSet(common.Hash{})
+
+ err := root.CollectNodes(nil, func(path []byte, node bintrie.BinaryNode) {
+ serialized := bintrie.SerializeNode(node)
+ nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
+ })
+ if err != nil {
+ panic(fmt.Errorf("CollectNodes failed: %v", err))
+ }
+
+ // Serialize root commitment form
+ return trie.Hash(), nodeset, nil
+}
+
+// NodeIterator returns an iterator that returns nodes of the trie. Iteration
+// starts at the key after the given start key.
+func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
+ return newBinaryNodeIterator(trie, nil)
+}
+
+// Prove constructs a Merkle proof for key. The result contains all encoded nodes
+// on the path to the value at key. The value itself is also included in the last
+// node and can be retrieved by verifying the proof.
+//
+// If the trie does not contain a value for key, the returned proof contains all
+// nodes of the longest existing prefix of the key (at least the root), ending
+// with the node that proves the absence of the key.
+func (trie *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
+ panic("not implemented")
+}
+
+// Copy creates a deep copy of the trie.
+func (trie *BinaryTrie) Copy() *BinaryTrie {
+ return &BinaryTrie{
+ root: trie.root.Copy(),
+ reader: trie.reader,
+ }
+}
+
+// IsVerkle returns true if the trie is a Verkle tree.
+func (trie *BinaryTrie) IsVerkle() bool {
+ // TODO @gballet This is technically NOT a verkle tree, but it has the same
+ // behavior and basic structure, so for all intents and purposes, it can be
+ // treated as such. Rename this when verkle gets removed.
+ return true
+}
+
+// Note: the basic data leaf needs to have been previously created for this to work
+func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
+ var (
+ chunks = ChunkifyCode(code)
+ values [][]byte
+ key []byte
+ err error
+ )
+ for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
+ groupOffset := (chunknr + 128) % 256
+ if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
+ values = make([][]byte, bintrie.NodeWidth)
+ var offset [32]byte
+ binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
+ key = utils.GetBinaryTreeKey(addr, offset[:])
+ }
+ values[groupOffset] = chunks[i : i+32]
+
+ if groupOffset == 255 || len(chunks)-i <= 32 {
+ err = trie.UpdateStem(key[:31], values)
+
+ if err != nil {
+ return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
+ }
+ }
+ }
+ return nil
+}
diff --git a/trie/binary_iterator.go b/trie/binary_iterator.go
new file mode 100644
index 00000000000..e4799d207ab
--- /dev/null
+++ b/trie/binary_iterator.go
@@ -0,0 +1,236 @@
+// Copyright 2025 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 trie
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/trie/bintrie"
+)
+
+type binaryNodeIteratorState struct {
+ Node bintrie.BinaryNode
+ Index int
+}
+
+type binaryNodeIterator struct {
+ trie *BinaryTrie
+ current bintrie.BinaryNode
+ lastErr error
+
+ stack []binaryNodeIteratorState
+}
+
+func newBinaryNodeIterator(trie *BinaryTrie, _ []byte) (NodeIterator, error) {
+ if trie.Hash() == zero {
+ return new(nodeIterator), nil
+ }
+ it := &binaryNodeIterator{trie: trie, current: trie.root}
+ // it.err = it.seek(start)
+ return it, nil
+}
+
+// Next moves the iterator to the next node. If the parameter is false, any child
+// nodes will be skipped.
+func (it *binaryNodeIterator) Next(descend bool) bool {
+ if it.lastErr == errIteratorEnd {
+ it.lastErr = errIteratorEnd
+ return false
+ }
+
+ if len(it.stack) == 0 {
+ it.stack = append(it.stack, binaryNodeIteratorState{Node: it.trie.root})
+ it.current = it.trie.root
+
+ return true
+ }
+
+ switch node := it.current.(type) {
+ case *bintrie.InternalNode:
+ // index: 0 = nothing visited, 1=left visited, 2=right visited
+ context := &it.stack[len(it.stack)-1]
+
+ // recurse into both children
+ if context.Index == 0 {
+ if _, isempty := node.Left.(bintrie.Empty); node.Left != nil && !isempty {
+ it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
+ it.current = node.Left
+ return it.Next(descend)
+ }
+
+ context.Index++
+ }
+
+ if context.Index == 1 {
+ if _, isempty := node.Right.(bintrie.Empty); node.Right != nil && !isempty {
+ it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
+ it.current = node.Right
+ return it.Next(descend)
+ }
+
+ context.Index++
+ }
+
+ // Reached the end of this node, go back to the parent, if
+ // this isn't root.
+ if len(it.stack) == 1 {
+ it.lastErr = errIteratorEnd
+ return false
+ }
+ it.stack = it.stack[:len(it.stack)-1]
+ it.current = it.stack[len(it.stack)-1].Node
+ it.stack[len(it.stack)-1].Index++
+ return it.Next(descend)
+ case *bintrie.StemNode:
+ // Look for the next non-empty value
+ for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
+ if node.Values[i] != nil {
+ it.stack[len(it.stack)-1].Index = i + 1
+ return true
+ }
+ }
+
+ // go back to parent to get the next leaf
+ it.stack = it.stack[:len(it.stack)-1]
+ it.current = it.stack[len(it.stack)-1].Node
+ it.stack[len(it.stack)-1].Index++
+ return it.Next(descend)
+ case bintrie.HashedNode:
+ // resolve the node
+ data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
+ if err != nil {
+ panic(err)
+ }
+ it.current, err = bintrie.DeserializeNode(data, len(it.stack)-1)
+ if err != nil {
+ panic(err)
+ }
+
+ // update the stack and parent with the resolved node
+ it.stack[len(it.stack)-1].Node = it.current
+ parent := &it.stack[len(it.stack)-2]
+ if parent.Index == 0 {
+ parent.Node.(*bintrie.InternalNode).Left = it.current
+ } else {
+ parent.Node.(*bintrie.InternalNode).Right = it.current
+ }
+ return it.Next(descend)
+ case bintrie.Empty:
+ // do nothing
+ return false
+ default:
+ panic("invalid node type")
+ }
+}
+
+// Error returns the error status of the iterator.
+func (it *binaryNodeIterator) Error() error {
+ if it.lastErr == errIteratorEnd {
+ return nil
+ }
+ return it.lastErr
+}
+
+// Hash returns the hash of the current node.
+func (it *binaryNodeIterator) Hash() common.Hash {
+ return it.current.Hash()
+}
+
+// Parent returns the hash of the parent of the current node. The hash may be the one
+// grandparent if the immediate parent is an internal node with no hash.
+func (it *binaryNodeIterator) Parent() common.Hash {
+ return it.stack[len(it.stack)-1].Node.Hash()
+}
+
+// Path returns the hex-encoded path to the current node.
+// Callers must not retain references to the return value after calling Next.
+// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
+func (it *binaryNodeIterator) Path() []byte {
+ if it.Leaf() {
+ return it.LeafKey()
+ }
+ var path []byte
+ for i, state := range it.stack {
+ // skip the last byte
+ if i >= len(it.stack)-1 {
+ break
+ }
+ path = append(path, byte(state.Index))
+ }
+ return path
+}
+
+func (it *binaryNodeIterator) NodeBlob() []byte {
+ panic("not completely implemented")
+}
+
+// Leaf returns true iff the current node is a leaf node.
+func (it *binaryNodeIterator) Leaf() bool {
+ _, ok := it.current.(*bintrie.StemNode)
+ return ok
+}
+
+// LeafKey returns the key of the leaf. The method panics if the iterator is not
+// positioned at a leaf. Callers must not retain references to the value after
+// calling Next.
+func (it *binaryNodeIterator) LeafKey() []byte {
+ leaf, ok := it.current.(*bintrie.StemNode)
+ if !ok {
+ panic("Leaf() called on an binary node iterator not at a leaf location")
+ }
+
+ return leaf.Key(it.stack[len(it.stack)-1].Index - 1)
+}
+
+// LeafBlob returns the content of the leaf. The method panics if the iterator
+// is not positioned at a leaf. Callers must not retain references to the value
+// after calling Next.
+func (it *binaryNodeIterator) LeafBlob() []byte {
+ leaf, ok := it.current.(*bintrie.StemNode)
+ if !ok {
+ panic("LeafBlob() called on an binary node iterator not at a leaf location")
+ }
+
+ return leaf.Values[it.stack[len(it.stack)-1].Index-1]
+}
+
+// LeafProof returns the Merkle proof of the leaf. The method panics if the
+// iterator is not positioned at a leaf. Callers must not retain references
+// to the value after calling Next.
+func (it *binaryNodeIterator) LeafProof() [][]byte {
+ _, ok := it.current.(*bintrie.StemNode)
+ if !ok {
+ panic("LeafProof() called on an binary node iterator not at a leaf location")
+ }
+
+ // return it.trie.Prove(leaf.Key())
+ panic("not completely implemented")
+}
+
+// AddResolver sets an intermediate database to use for looking up trie nodes
+// before reaching into the real persistent layer.
+//
+// This is not required for normal operation, rather is an optimization for
+// cases where trie nodes can be recovered from some external mechanism without
+// reading from disk. In those cases, this resolver allows short circuiting
+// accesses and returning them from memory.
+//
+// Before adding a similar mechanism to any other place in Geth, consider
+// making trie.Database an interface and wrapping at that level. It's a huge
+// refactor, but it could be worth it if another occurrence arises.
+func (it *binaryNodeIterator) AddResolver(NodeResolver) {
+ // Not implemented, but should not panic
+}
diff --git a/trie/binary_iterator_test.go b/trie/binary_iterator_test.go
new file mode 100644
index 00000000000..01a4373cd24
--- /dev/null
+++ b/trie/binary_iterator_test.go
@@ -0,0 +1,69 @@
+// Copyright 2025 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 trie
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/holiman/uint256"
+)
+
+func TestBinaryIterator(t *testing.T) {
+ trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme))
+ if err != nil {
+ t.Fatal(err)
+ }
+ account0 := &types.StateAccount{
+ Nonce: 1,
+ Balance: uint256.NewInt(2),
+ Root: types.EmptyRootHash,
+ CodeHash: nil,
+ }
+ // NOTE: the code size isn't written to the trie via TryUpdateAccount
+ // so it will be missing from the test nodes.
+ trie.UpdateAccount(common.Address{}, account0, 0)
+ account1 := &types.StateAccount{
+ Nonce: 1337,
+ Balance: uint256.NewInt(2000),
+ Root: types.EmptyRootHash,
+ CodeHash: nil,
+ }
+ // This address is meant to hash to a value that has the same first byte as 0xbf
+ var clash = common.HexToAddress("69fd8034cdb20934dedffa7dccb4fb3b8062a8be")
+ trie.UpdateAccount(clash, account1, 0)
+
+ // Manually go over every node to check that we get all
+ // the correct nodes.
+ it, err := trie.NodeIterator(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var leafcount int
+ for it.Next(true) {
+ t.Logf("Node: %x", it.Path())
+ if it.Leaf() {
+ leafcount++
+ t.Logf("\tLeaf: %x", it.LeafKey())
+ }
+ }
+ if leafcount != 2 {
+ t.Fatalf("invalid leaf count: %d != 6", leafcount)
+ }
+}
diff --git a/trie/binary_test.go b/trie/binary_test.go
new file mode 100644
index 00000000000..082ca9b3030
--- /dev/null
+++ b/trie/binary_test.go
@@ -0,0 +1,198 @@
+// Copyright 2025 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 trie
+
+import (
+ "bytes"
+ "encoding/binary"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/trie/bintrie"
+)
+
+var (
+ zeroKey = [32]byte{}
+ oneKey = common.HexToHash("0101010101010101010101010101010101010101010101010101010101010101")
+ twoKey = common.HexToHash("0202020202020202020202020202020202020202020202020202020202020202")
+ threeKey = common.HexToHash("0303030303030303030303030303030303030303030303030303030303030303")
+ fourKey = common.HexToHash("0404040404040404040404040404040404040404040404040404040404040404")
+ ffKey = common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+)
+
+func TestSingleEntry(t *testing.T) {
+ tree := NewBinaryNode()
+ tree, err := tree.Insert(zeroKey[:], oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tree.GetHeight() != 1 {
+ t.Fatal("invalid depth")
+ }
+ expected := common.HexToHash("694545468677064fd833cddc8455762fe6b21c6cabe2fc172529e0f573181cd5")
+ got := tree.Hash()
+ if got != expected {
+ t.Fatalf("invalid tree root, got %x, want %x", got, expected)
+ }
+}
+
+func TestTwoEntriesDiffFirstBit(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ tree, err = tree.Insert(zeroKey[:], oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000000").Bytes(), twoKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tree.GetHeight() != 2 {
+ t.Fatal("invalid height")
+ }
+ if tree.Hash() != common.HexToHash("85fc622076752a6fcda2c886c18058d639066a83473d9684704b5a29455ed2ed") {
+ t.Fatal("invalid tree root")
+ }
+}
+
+func TestOneStemColocatedValues(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000009").Bytes(), threeKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(common.HexToHash("00000000000000000000000000000000000000000000000000000000000000FF").Bytes(), fourKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tree.GetHeight() != 1 {
+ t.Fatal("invalid height")
+ }
+}
+
+func TestTwoStemColocatedValues(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ // stem: 0...0
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // stem: 10...0
+ tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tree.GetHeight() != 2 {
+ t.Fatal("invalid height")
+ }
+}
+
+func TestTwoKeysMatchFirst42Bits(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ // key1 and key 2 have the same prefix of 42 bits (b0*42+b1+b1) and differ after.
+ key1 := common.HexToHash("0000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0").Bytes()
+ key2 := common.HexToHash("0000000000E00000000000000000000000000000000000000000000000000000").Bytes()
+ tree, err = tree.Insert(key1, oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(key2, twoKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tree.GetHeight() != 1+42+1 {
+ t.Fatal("invalid height")
+ }
+}
+func TestInsertDuplicateKey(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ tree, err = tree.Insert(oneKey[:], oneKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tree, err = tree.Insert(oneKey[:], twoKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tree.GetHeight() != 1 {
+ t.Fatal("invalid height")
+ }
+ // Verify that the value is updated
+ if !bytes.Equal(tree.(*bintrie.StemNode).Values[1], twoKey[:]) {
+ t.Fatal("invalid height")
+ }
+}
+func TestLargeNumberOfEntries(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ for i := range 256 {
+ var key [32]byte
+ key[0] = byte(i)
+ tree, err = tree.Insert(key[:], ffKey[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ height := tree.GetHeight()
+ if height != 1+8 {
+ t.Fatalf("invalid height, wanted %d, got %d", 1+8, height)
+ }
+}
+
+func TestMerkleizeMultipleEntries(t *testing.T) {
+ var err error
+ tree := NewBinaryNode()
+ keys := [][]byte{
+ zeroKey[:],
+ common.HexToHash("8000000000000000000000000000000000000000000000000000000000000000").Bytes(),
+ common.HexToHash("0100000000000000000000000000000000000000000000000000000000000000").Bytes(),
+ common.HexToHash("8100000000000000000000000000000000000000000000000000000000000000").Bytes(),
+ }
+ for i, key := range keys {
+ var v [32]byte
+ binary.LittleEndian.PutUint64(v[:8], uint64(i))
+ tree, err = tree.Insert(key, v[:], nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ got := tree.Hash()
+ expected := common.HexToHash("8c74de28e6bb6b2296cae37cff16266e2dbf533bc204fa4cb0c237761ae8a2c8")
+ if got != expected {
+ t.Fatalf("invalid root, expected=%x, got = %x", expected, got)
+ }
+}
diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go
new file mode 100644
index 00000000000..b97adeb21e2
--- /dev/null
+++ b/trie/bintrie/binary_node.go
@@ -0,0 +1,121 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type (
+ NodeFlushFn func([]byte, BinaryNode)
+ NodeResolverFn func([]byte, common.Hash) ([]byte, error)
+)
+
+// zero is the zero value for a 32-byte array.
+var zero [32]byte
+
+const (
+ NodeWidth = 256 // Number of child per leaf node
+ StemSize = 31 // Number of bytes to travel before reaching a group of leaves
+)
+
+// BinaryNode is an interface for a binary trie node.
+type BinaryNode interface {
+ Get([]byte, NodeResolverFn) ([]byte, error)
+ Insert([]byte, []byte, NodeResolverFn) (BinaryNode, error)
+ // Commit() common.Hash
+ Copy() BinaryNode
+ Hash() common.Hash
+ GetValuesAtStem([]byte, NodeResolverFn) ([][]byte, error)
+ InsertValuesAtStem([]byte, [][]byte, NodeResolverFn, int) (BinaryNode, error)
+ CollectNodes([]byte, NodeFlushFn) error
+
+ toDot(parent, path string) string
+ GetHeight() int
+}
+
+// SerializeNode serializes a binary trie node into a byte slice.
+func SerializeNode(node BinaryNode) []byte {
+ switch n := (node).(type) {
+ case *InternalNode:
+ var serialized [65]byte
+ serialized[0] = 1
+ copy(serialized[1:33], n.Left.Hash().Bytes())
+ copy(serialized[33:65], n.Right.Hash().Bytes())
+ return serialized[:]
+ case *StemNode:
+ var serialized [32 + 256*32]byte
+ serialized[0] = 2
+ copy(serialized[1:32], node.(*StemNode).Stem)
+ bitmap := serialized[32:64]
+ offset := 64
+ for i, v := range node.(*StemNode).Values {
+ if v != nil {
+ bitmap[i/8] |= 1 << (7 - (i % 8))
+ copy(serialized[offset:offset+32], v)
+ offset += 32
+ }
+ }
+ return serialized[:]
+ default:
+ panic("invalid node type")
+ }
+}
+
+// DeserializeNode deserializes a binary trie node from a byte slice.
+func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
+ if len(serialized) == 0 {
+ return Empty{}, nil
+ }
+
+ switch serialized[0] {
+ case 1:
+ if len(serialized) != 65 {
+ return nil, errors.New("invalid serialized node length")
+ }
+ return &InternalNode{
+ depth: depth,
+ Left: HashedNode(common.BytesToHash(serialized[1:33])),
+ Right: HashedNode(common.BytesToHash(serialized[33:65])),
+ }, nil
+ case 2:
+ var values [256][]byte
+ bitmap := serialized[32:64]
+ offset := 64
+
+ for i := range 256 {
+ if bitmap[i/8]>>(7-(i%8))&1 == 1 {
+ values[i] = serialized[offset : offset+32]
+ offset += 32
+ }
+ }
+ return &StemNode{
+ Stem: serialized[1:32],
+ Values: values[:],
+ depth: depth,
+ }, nil
+ default:
+ return nil, errors.New("invalid node type")
+ }
+}
+
+// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
+func ToDot(root BinaryNode) string {
+ return root.toDot("", "")
+}
diff --git a/trie/bintrie/empty.go b/trie/bintrie/empty.go
new file mode 100644
index 00000000000..1effd7f03a0
--- /dev/null
+++ b/trie/bintrie/empty.go
@@ -0,0 +1,71 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "slices"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type Empty struct{}
+
+func (e Empty) Get(_ []byte, _ NodeResolverFn) ([]byte, error) {
+ return nil, nil
+}
+
+func (e Empty) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNode, error) {
+ var values [256][]byte
+ values[key[31]] = value
+ return &StemNode{
+ Stem: slices.Clone(key[:31]),
+ Values: values[:],
+ }, nil
+}
+
+func (e Empty) Copy() BinaryNode {
+ return Empty{}
+}
+
+func (e Empty) Hash() common.Hash {
+ return common.Hash{}
+}
+
+func (e Empty) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) {
+ var values [256][]byte
+ return values[:], nil
+}
+
+func (e Empty) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
+ return &StemNode{
+ Stem: slices.Clone(key[:31]),
+ Values: values,
+ depth: depth,
+ }, nil
+}
+
+func (e Empty) CollectNodes(_ []byte, _ NodeFlushFn) error {
+ return nil
+}
+
+func (e Empty) toDot(parent string, path string) string {
+ return ""
+}
+
+func (e Empty) GetHeight() int {
+ return 0
+}
diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go
new file mode 100644
index 00000000000..e5ee79a460a
--- /dev/null
+++ b/trie/bintrie/hashed_node.go
@@ -0,0 +1,66 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+type HashedNode common.Hash
+
+func (h HashedNode) Get(_ []byte, _ NodeResolverFn) ([]byte, error) {
+ panic("not implemented") // TODO: Implement
+}
+
+func (h HashedNode) Insert(key []byte, value []byte, resolver NodeResolverFn) (BinaryNode, error) {
+ return nil, errors.New("insert not implemented for hashed node")
+}
+
+func (h HashedNode) Copy() BinaryNode {
+ nh := common.Hash(h)
+ return HashedNode(nh)
+}
+
+func (h HashedNode) Hash() common.Hash {
+ return common.Hash(h)
+}
+
+func (h HashedNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) {
+ return nil, errors.New("attempted to get values from an unresolved node")
+}
+
+func (h HashedNode) InsertValuesAtStem(key []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
+ return nil, errors.New("insertValuesAtStem not implemented for hashed node")
+}
+
+func (h HashedNode) toDot(parent string, path string) string {
+ me := fmt.Sprintf("hash%s", path)
+ ret := fmt.Sprintf("%s [label=\"%x\"]\n", me, h)
+ ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
+ return ret
+}
+
+func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error {
+ panic("not implemented") // TODO: Implement
+}
+
+func (h HashedNode) GetHeight() int {
+ panic("tried to get the height of a hashed node, this is a bug")
+}
diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go
new file mode 100644
index 00000000000..6287606c50e
--- /dev/null
+++ b/trie/bintrie/internal_node.go
@@ -0,0 +1,194 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/zeebo/blake3"
+)
+
+func keyToPath(depth int, key []byte) ([]byte, error) {
+ path := make([]byte, 0, depth+1)
+
+ if depth > 31*8 {
+ return nil, errors.New("node too deep")
+ }
+
+ for i := range depth + 1 {
+ bit := key[i/8] >> (7 - (i % 8)) & 1
+ path = append(path, bit)
+ }
+
+ return path, nil
+}
+
+// InternalNode is a binary trie internal node.
+type InternalNode struct {
+ Left, Right BinaryNode
+ depth int
+}
+
+// GetValuesAtStem retrieves the group of values located at the given stem key.
+func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([][]byte, error) {
+ if bt.depth > 31*8 {
+ return nil, errors.New("node too deep")
+ }
+
+ bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
+ var child *BinaryNode
+ if bit == 0 {
+ child = &bt.Left
+ } else {
+ child = &bt.Right
+ }
+
+ if hn, ok := (*child).(HashedNode); ok {
+ path, err := keyToPath(bt.depth, stem)
+ if err != nil {
+ return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
+ }
+ data, err := resolver(path, common.Hash(hn))
+ if err != nil {
+ return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err)
+ }
+ node, err := DeserializeNode(data, bt.depth+1)
+ if err != nil {
+ return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err)
+ }
+ *child = node
+ }
+ return (*child).GetValuesAtStem(stem, resolver)
+}
+
+// Get retrieves the value for the given key.
+func (bt *InternalNode) Get(key []byte, resolver NodeResolverFn) ([]byte, error) {
+ values, err := bt.GetValuesAtStem(key[:31], resolver)
+ if err != nil {
+ return nil, fmt.Errorf("Get error: %w", err)
+ }
+ return values[key[31]], nil
+}
+
+// Insert inserts a new key-value pair into the trie.
+func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn) (BinaryNode, error) {
+ var values [256][]byte
+ values[key[31]] = value
+ return bt.InsertValuesAtStem(key[:31], values[:], resolver, 0)
+}
+
+// Copy creates a deep copy of the node.
+func (bt *InternalNode) Copy() BinaryNode {
+ return &InternalNode{
+ Left: bt.Left.Copy(),
+ Right: bt.Right.Copy(),
+ depth: bt.depth,
+ }
+}
+
+// Hash returns the hash of the node.
+func (bt *InternalNode) Hash() common.Hash {
+ h := blake3.New()
+ if bt.Left != nil {
+ h.Write(bt.Left.Hash().Bytes())
+ } else {
+ h.Write(zero[:])
+ }
+ if bt.Right != nil {
+ h.Write(bt.Right.Hash().Bytes())
+ } else {
+ h.Write(zero[:])
+ }
+ return common.BytesToHash(h.Sum(nil))
+}
+
+// InsertValuesAtStem inserts a full value group at the given stem in the internal node.
+// Already-existing values will be overwritten.
+func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
+ bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
+ var (
+ child *BinaryNode
+ err error
+ )
+ if bit == 0 {
+ child = &bt.Left
+ } else {
+ child = &bt.Right
+ }
+
+ *child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
+ return bt, err
+}
+
+// CollectNodes collects all child nodes at a given path, and flushes it
+// into the provided node collector.
+func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error {
+ if bt.Left != nil {
+ var p [256]byte
+ copy(p[:], path)
+ childpath := p[:len(path)]
+ childpath = append(childpath, 0)
+ if err := bt.Left.CollectNodes(childpath, flushfn); err != nil {
+ return err
+ }
+ }
+ if bt.Right != nil {
+ var p [256]byte
+ copy(p[:], path)
+ childpath := p[:len(path)]
+ childpath = append(childpath, 1)
+ if err := bt.Right.CollectNodes(childpath, flushfn); err != nil {
+ return err
+ }
+ }
+ flushfn(path, bt)
+ return nil
+}
+
+// GetHeight returns the height of the node.
+func (bt *InternalNode) GetHeight() int {
+ var (
+ leftHeight int
+ rightHeight int
+ )
+ if bt.Left != nil {
+ leftHeight = bt.Left.GetHeight()
+ }
+ if bt.Right != nil {
+ rightHeight = bt.Right.GetHeight()
+ }
+ return 1 + max(leftHeight, rightHeight)
+}
+
+func (bt *InternalNode) toDot(parent, path string) string {
+ me := fmt.Sprintf("internal%s", path)
+ ret := fmt.Sprintf("%s [label=\"I: %x\"]\n", me, bt.Hash())
+ if len(parent) > 0 {
+ ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
+ }
+
+ if bt.Left != nil {
+ ret = fmt.Sprintf("%s%s", ret, bt.Left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
+ }
+ if bt.Right != nil {
+ ret = fmt.Sprintf("%s%s", ret, bt.Right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
+ }
+
+ return ret
+}
diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go
new file mode 100644
index 00000000000..568d3ff4f26
--- /dev/null
+++ b/trie/bintrie/stem_node.go
@@ -0,0 +1,216 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "slices"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/zeebo/blake3"
+)
+
+// StemNode represents a group of `NodeWith` values sharing the same stem.
+type StemNode struct {
+ Stem []byte // Stem path to get to 256 values
+ Values [][]byte // All values, indexed by the last byte of the key.
+ depth int // Depth of the node
+}
+
+// Get retrieves the value for the given key.
+func (bt *StemNode) Get(key []byte, _ NodeResolverFn) ([]byte, error) {
+ panic("this should not be called directly")
+}
+
+// Insert inserts a new key-value pair into the node.
+func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNode, error) {
+ if !bytes.Equal(bt.Stem, key[:31]) {
+ bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
+
+ new := &InternalNode{depth: bt.depth}
+ bt.depth++
+ var child, other *BinaryNode
+ if bitStem == 0 {
+ new.Left = bt
+ child = &new.Left
+ other = &new.Right
+ } else {
+ new.Right = bt
+ child = &new.Right
+ other = &new.Left
+ }
+
+ bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
+ if bitKey == bitStem {
+ var err error
+ *child, err = (*child).Insert(key, value, nil)
+ if err != nil {
+ return new, fmt.Errorf("insert error: %w", err)
+ }
+ *other = Empty{}
+ } else {
+ var values [256][]byte
+ values[key[31]] = value
+ *other = &StemNode{
+ Stem: slices.Clone(key[:31]),
+ Values: values[:],
+ depth: new.depth + 1,
+ }
+ }
+
+ return new, nil
+ }
+ if len(value) != 32 {
+ return bt, errors.New("invalid insertion: value length")
+ }
+
+ bt.Values[key[31]] = value
+ return bt, nil
+}
+
+// Copy creates a deep copy of the node.
+func (bt *StemNode) Copy() BinaryNode {
+ var values [256][]byte
+ for i, v := range bt.Values {
+ values[i] = slices.Clone(v)
+ }
+ return &StemNode{
+ Stem: slices.Clone(bt.Stem),
+ Values: values[:],
+ depth: bt.depth,
+ }
+}
+
+// GetHeight returns the height of the node.
+func (bt *StemNode) GetHeight() int {
+ return 1
+}
+
+// Hash returns the hash of the node.
+func (bt *StemNode) Hash() common.Hash {
+ var data [NodeWidth]common.Hash
+ for i, v := range bt.Values {
+ if v != nil {
+ h := blake3.Sum256(v)
+ data[i] = common.BytesToHash(h[:])
+ }
+ }
+
+ h := blake3.New()
+ for level := 1; level <= 8; level++ {
+ for i := range NodeWidth / (1 << level) {
+ h.Reset()
+
+ if data[i*2] == (common.Hash{}) && data[i*2+1] == (common.Hash{}) {
+ data[i] = common.Hash{}
+ continue
+ }
+
+ h.Write(data[i*2][:])
+ h.Write(data[i*2+1][:])
+ data[i] = common.Hash(h.Sum(nil))
+ }
+ }
+
+ h.Reset()
+ h.Write(bt.Stem)
+ h.Write([]byte{0})
+ h.Write(data[0][:])
+ return common.BytesToHash(h.Sum(nil))
+}
+
+// CollectNodes collects all child nodes at a given path, and flushes it
+// into the provided node collector.
+func (bt *StemNode) CollectNodes(path []byte, flush NodeFlushFn) error {
+ flush(path, bt)
+ return nil
+}
+
+// GetValuesAtStem retrieves the group of values located at the given stem key.
+func (bt *StemNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) {
+ return bt.Values[:], nil
+}
+
+// InsertValuesAtStem inserts a full value group at the given stem in the internal node.
+// Already-existing values will be overwritten.
+func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
+ if !bytes.Equal(bt.Stem, key[:31]) {
+ bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
+
+ new := &InternalNode{depth: bt.depth}
+ bt.depth++
+ var child, other *BinaryNode
+ if bitStem == 0 {
+ new.Left = bt
+ child = &new.Left
+ other = &new.Right
+ } else {
+ new.Right = bt
+ child = &new.Right
+ other = &new.Left
+ }
+
+ bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
+ if bitKey == bitStem {
+ var err error
+ *child, err = (*child).InsertValuesAtStem(key, values, nil, depth+1)
+ if err != nil {
+ return new, fmt.Errorf("insert error: %w", err)
+ }
+ *other = Empty{}
+ } else {
+ *other = &StemNode{
+ Stem: slices.Clone(key[:31]),
+ Values: values,
+ depth: new.depth + 1,
+ }
+ }
+
+ return new, nil
+ }
+
+ // same stem, just merge the two value lists
+ for i, v := range values {
+ if v != nil {
+ bt.Values[i] = v
+ }
+ }
+ return bt, nil
+}
+
+func (bt *StemNode) toDot(parent, path string) string {
+ me := fmt.Sprintf("stem%s", path)
+ ret := fmt.Sprintf("%s [label=\"stem=%x c=%x\"]\n", me, bt.Stem, bt.Hash())
+ ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
+ for i, v := range bt.Values {
+ if v != nil {
+ ret = fmt.Sprintf("%s%s%x [label=\"%x\"]\n", ret, me, i, v)
+ ret = fmt.Sprintf("%s%s -> %s%x\n", ret, me, me, i)
+ }
+ }
+ return ret
+}
+
+// Key returns the full key for the given index.
+func (bt *StemNode) Key(i int) []byte {
+ var ret [32]byte
+ copy(ret[:], bt.Stem)
+ ret[StemSize] = byte(i)
+ return ret[:]
+}
diff --git a/trie/utils/binary.go b/trie/utils/binary.go
new file mode 100644
index 00000000000..d50b5d4167e
--- /dev/null
+++ b/trie/utils/binary.go
@@ -0,0 +1,68 @@
+// Copyright 2025 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 utils
+
+import (
+ "bytes"
+ "crypto/sha256"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/holiman/uint256"
+)
+
+var zeroHash = common.Hash{}
+
+func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
+ hasher := sha256.New()
+ hasher.Write(zeroHash[:12])
+ hasher.Write(addr[:])
+ hasher.Write(key[:31])
+ k := hasher.Sum(nil)
+ k[31] = key[31]
+ return k
+}
+
+func GetBinaryTreeKeyCodeHash(addr common.Address) []byte {
+ var k [32]byte
+ k[31] = CodeHashLeafKey
+ return GetBinaryTreeKey(addr, k[:])
+}
+
+func GetBinaryTreeKeyStorageSlot(address common.Address, key []byte) []byte {
+ var k [32]byte
+
+ // Case when the key belongs to the account header
+ if bytes.Equal(key[:31], zeroHash[:31]) && key[31] < 64 {
+ k[31] = 64 + key[31]
+ return GetBinaryTreeKey(address, k[:])
+ }
+
+ // Set the main storage offset
+ // note that the first 64 bytes of the main offset storage
+ // are unreachable, which is consistent with the spec and
+ // what verkle does.
+ k[0] = 1 // 1 << 248
+ copy(k[1:], key[:31])
+ k[31] = key[31]
+
+ return GetBinaryTreeKey(address, k[:])
+}
+
+func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []byte {
+ chunkOffset := new(uint256.Int).Add(codeOffset, chunknr).Bytes()
+ return GetBinaryTreeKey(address, chunkOffset)
+}
From f13dfc5baa6d8866ba0c1f3ced994e9be8338158 Mon Sep 17 00:00:00 2001
From: Parithosh Jayanthi
Date: Wed, 13 Aug 2025 19:57:33 +0200
Subject: [PATCH 02/10] Move BinaryTrie and iterator to the bintrie package and
add more coverage (#547)
* refactor package
* add tests
---
trie/bintrie/binary_node_test.go | 269 ++++++++++
trie/bintrie/empty_test.go | 222 +++++++++
trie/bintrie/hashed_node_test.go | 174 +++++++
trie/bintrie/internal_node_test.go | 458 ++++++++++++++++++
.../iterator.go} | 48 +-
.../iterator_test.go} | 16 +-
trie/bintrie/stem_node_test.go | 373 ++++++++++++++
trie/{binary.go => bintrie/trie.go} | 114 ++---
trie/{binary_test.go => bintrie/trie_test.go} | 5 +-
trie/iterator.go | 4 +-
trie/proof.go | 2 +-
trie/trie.go | 8 +-
trie/trie_reader.go | 18 +-
trie/verkle.go | 10 +-
14 files changed, 1619 insertions(+), 102 deletions(-)
create mode 100644 trie/bintrie/binary_node_test.go
create mode 100644 trie/bintrie/empty_test.go
create mode 100644 trie/bintrie/hashed_node_test.go
create mode 100644 trie/bintrie/internal_node_test.go
rename trie/{binary_iterator.go => bintrie/iterator.go} (86%)
rename trie/{binary_iterator_test.go => bintrie/iterator_test.go} (80%)
create mode 100644 trie/bintrie/stem_node_test.go
rename trie/{binary.go => bintrie/trie.go} (74%)
rename trie/{binary_test.go => bintrie/trie_test.go} (97%)
diff --git a/trie/bintrie/binary_node_test.go b/trie/bintrie/binary_node_test.go
new file mode 100644
index 00000000000..5db6e111f08
--- /dev/null
+++ b/trie/bintrie/binary_node_test.go
@@ -0,0 +1,269 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "bytes"
+ "errors"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// TestSerializeDeserializeInternalNode tests serialization and deserialization of InternalNode
+func TestSerializeDeserializeInternalNode(t *testing.T) {
+ // Create an internal node with two hashed children
+ leftHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
+ rightHash := common.HexToHash("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
+
+ node := &InternalNode{
+ depth: 5,
+ Left: HashedNode(leftHash),
+ Right: HashedNode(rightHash),
+ }
+
+ // Serialize the node
+ serialized := SerializeNode(node)
+
+ // Check the serialized format
+ if serialized[0] != 1 {
+ t.Errorf("Expected type byte to be 1, got %d", serialized[0])
+ }
+
+ if len(serialized) != 65 {
+ t.Errorf("Expected serialized length to be 65, got %d", len(serialized))
+ }
+
+ // Deserialize the node
+ deserialized, err := DeserializeNode(serialized, 5)
+ if err != nil {
+ t.Fatalf("Failed to deserialize node: %v", err)
+ }
+
+ // Check that it's an internal node
+ internalNode, ok := deserialized.(*InternalNode)
+ if !ok {
+ t.Fatalf("Expected InternalNode, got %T", deserialized)
+ }
+
+ // Check the depth
+ if internalNode.depth != 5 {
+ t.Errorf("Expected depth 5, got %d", internalNode.depth)
+ }
+
+ // Check the left and right hashes
+ if internalNode.Left.Hash() != leftHash {
+ t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.Left.Hash())
+ }
+
+ if internalNode.Right.Hash() != rightHash {
+ t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.Right.Hash())
+ }
+}
+
+// TestSerializeDeserializeStemNode tests serialization and deserialization of StemNode
+func TestSerializeDeserializeStemNode(t *testing.T) {
+ // Create a stem node with some values
+ stem := make([]byte, 31)
+ for i := range stem {
+ stem[i] = byte(i)
+ }
+
+ var values [256][]byte
+ // Add some values at different indices
+ values[0] = common.HexToHash("0x0101010101010101010101010101010101010101010101010101010101010101").Bytes()
+ values[10] = common.HexToHash("0x0202020202020202020202020202020202020202020202020202020202020202").Bytes()
+ values[255] = common.HexToHash("0x0303030303030303030303030303030303030303030303030303030303030303").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 10,
+ }
+
+ // Serialize the node
+ serialized := SerializeNode(node)
+
+ // Check the serialized format
+ if serialized[0] != 2 {
+ t.Errorf("Expected type byte to be 2, got %d", serialized[0])
+ }
+
+ // Check the stem is correctly serialized
+ if !bytes.Equal(serialized[1:32], stem) {
+ t.Errorf("Stem mismatch in serialized data")
+ }
+
+ // Deserialize the node
+ deserialized, err := DeserializeNode(serialized, 10)
+ if err != nil {
+ t.Fatalf("Failed to deserialize node: %v", err)
+ }
+
+ // Check that it's a stem node
+ stemNode, ok := deserialized.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected StemNode, got %T", deserialized)
+ }
+
+ // Check the stem
+ if !bytes.Equal(stemNode.Stem, stem) {
+ t.Errorf("Stem mismatch after deserialization")
+ }
+
+ // Check the values
+ if !bytes.Equal(stemNode.Values[0], values[0]) {
+ t.Errorf("Value at index 0 mismatch")
+ }
+ if !bytes.Equal(stemNode.Values[10], values[10]) {
+ t.Errorf("Value at index 10 mismatch")
+ }
+ if !bytes.Equal(stemNode.Values[255], values[255]) {
+ t.Errorf("Value at index 255 mismatch")
+ }
+
+ // Check that other values are nil
+ if stemNode.Values[1] != nil {
+ t.Errorf("Expected nil value at index 1, got %x", stemNode.Values[1])
+ }
+}
+
+// TestDeserializeEmptyNode tests deserialization of empty node
+func TestDeserializeEmptyNode(t *testing.T) {
+ // Empty byte slice should deserialize to Empty node
+ deserialized, err := DeserializeNode([]byte{}, 0)
+ if err != nil {
+ t.Fatalf("Failed to deserialize empty node: %v", err)
+ }
+
+ _, ok := deserialized.(Empty)
+ if !ok {
+ t.Fatalf("Expected Empty node, got %T", deserialized)
+ }
+}
+
+// TestDeserializeInvalidType tests deserialization with invalid type byte
+func TestDeserializeInvalidType(t *testing.T) {
+ // Create invalid serialized data with unknown type byte
+ invalidData := []byte{99, 0, 0, 0} // Type byte 99 is invalid
+
+ _, err := DeserializeNode(invalidData, 0)
+ if err == nil {
+ t.Fatal("Expected error for invalid type byte, got nil")
+ }
+}
+
+// TestDeserializeInvalidLength tests deserialization with invalid data length
+func TestDeserializeInvalidLength(t *testing.T) {
+ // InternalNode with type byte 1 but wrong length
+ invalidData := []byte{1, 0, 0} // Too short for internal node
+
+ _, err := DeserializeNode(invalidData, 0)
+ if err == nil {
+ t.Fatal("Expected error for invalid data length, got nil")
+ }
+
+ if err.Error() != "invalid serialized node length" {
+ t.Errorf("Expected 'invalid serialized node length' error, got: %v", err)
+ }
+}
+
+// TestKeyToPath tests the keyToPath function
+func TestKeyToPath(t *testing.T) {
+ tests := []struct {
+ name string
+ depth int
+ key []byte
+ expected []byte
+ wantErr bool
+ }{
+ {
+ name: "depth 0",
+ depth: 0,
+ key: []byte{0x80}, // 10000000 in binary
+ expected: []byte{1},
+ wantErr: false,
+ },
+ {
+ name: "depth 7",
+ depth: 7,
+ key: []byte{0xFF}, // 11111111 in binary
+ expected: []byte{1, 1, 1, 1, 1, 1, 1, 1},
+ wantErr: false,
+ },
+ {
+ name: "depth crossing byte boundary",
+ depth: 10,
+ key: []byte{0xFF, 0x00}, // 11111111 00000000 in binary
+ expected: []byte{1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
+ wantErr: false,
+ },
+ {
+ name: "max valid depth",
+ depth: 31 * 8,
+ key: make([]byte, 32),
+ expected: make([]byte, 31*8+1),
+ wantErr: false,
+ },
+ {
+ name: "depth too large",
+ depth: 31*8 + 1,
+ key: make([]byte, 32),
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ path, err := keyToPath(tt.depth, tt.key)
+ if tt.wantErr {
+ if err == nil {
+ t.Errorf("Expected error for depth %d, got nil", tt.depth)
+ }
+ return
+ }
+ if err != nil {
+ t.Errorf("Unexpected error: %v", err)
+ return
+ }
+ if !bytes.Equal(path, tt.expected) {
+ t.Errorf("Path mismatch: expected %v, got %v", tt.expected, path)
+ }
+ })
+ }
+}
+
+// Mock resolver function for testing
+func mockResolver(path []byte, hash common.Hash) ([]byte, error) {
+ // Return a simple stem node for testing
+ if hash == common.HexToHash("0x1234") {
+ stem := make([]byte, 31)
+ var values [256][]byte
+ values[0] = common.HexToHash("0xabcd").Bytes()
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ }
+ return SerializeNode(node), nil
+ }
+ return nil, errors.New("node not found")
+}
+
+// Mock flush function for testing
+func mockFlushFn(path []byte, node BinaryNode) {
+ // Just a stub for testing
+}
diff --git a/trie/bintrie/empty_test.go b/trie/bintrie/empty_test.go
new file mode 100644
index 00000000000..a3c3681cad6
--- /dev/null
+++ b/trie/bintrie/empty_test.go
@@ -0,0 +1,222 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// TestEmptyGet tests the Get method
+func TestEmptyGet(t *testing.T) {
+ node := Empty{}
+
+ key := make([]byte, 32)
+ value, err := node.Get(key, nil)
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ if value != nil {
+ t.Errorf("Expected nil value from empty node, got %x", value)
+ }
+}
+
+// TestEmptyInsert tests the Insert method
+func TestEmptyInsert(t *testing.T) {
+ node := Empty{}
+
+ key := make([]byte, 32)
+ key[0] = 0x12
+ key[31] = 0x34
+ value := common.HexToHash("0xabcd").Bytes()
+
+ newNode, err := node.Insert(key, value, nil)
+ if err != nil {
+ t.Fatalf("Failed to insert: %v", err)
+ }
+
+ // Should create a StemNode
+ stemNode, ok := newNode.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected StemNode, got %T", newNode)
+ }
+
+ // Check the stem (first 31 bytes of key)
+ if !bytes.Equal(stemNode.Stem, key[:31]) {
+ t.Errorf("Stem mismatch: expected %x, got %x", key[:31], stemNode.Stem)
+ }
+
+ // Check the value at the correct index (last byte of key)
+ if !bytes.Equal(stemNode.Values[key[31]], value) {
+ t.Errorf("Value mismatch at index %d: expected %x, got %x", key[31], value, stemNode.Values[key[31]])
+ }
+
+ // Check that other values are nil
+ for i := 0; i < 256; i++ {
+ if i != int(key[31]) && stemNode.Values[i] != nil {
+ t.Errorf("Expected nil value at index %d, got %x", i, stemNode.Values[i])
+ }
+ }
+}
+
+// TestEmptyCopy tests the Copy method
+func TestEmptyCopy(t *testing.T) {
+ node := Empty{}
+
+ copied := node.Copy()
+ copiedEmpty, ok := copied.(Empty)
+ if !ok {
+ t.Fatalf("Expected Empty, got %T", copied)
+ }
+
+ // Both should be empty
+ if node != copiedEmpty {
+ // Empty is a zero-value struct, so copies should be equal
+ t.Errorf("Empty nodes should be equal")
+ }
+}
+
+// TestEmptyHash tests the Hash method
+func TestEmptyHash(t *testing.T) {
+ node := Empty{}
+
+ hash := node.Hash()
+
+ // Empty node should have zero hash
+ if hash != (common.Hash{}) {
+ t.Errorf("Expected zero hash for empty node, got %x", hash)
+ }
+}
+
+// TestEmptyGetValuesAtStem tests the GetValuesAtStem method
+func TestEmptyGetValuesAtStem(t *testing.T) {
+ node := Empty{}
+
+ stem := make([]byte, 31)
+ values, err := node.GetValuesAtStem(stem, nil)
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // Should return an array of 256 nil values
+ if len(values) != 256 {
+ t.Errorf("Expected 256 values, got %d", len(values))
+ }
+
+ for i, v := range values {
+ if v != nil {
+ t.Errorf("Expected nil value at index %d, got %x", i, v)
+ }
+ }
+}
+
+// TestEmptyInsertValuesAtStem tests the InsertValuesAtStem method
+func TestEmptyInsertValuesAtStem(t *testing.T) {
+ node := Empty{}
+
+ stem := make([]byte, 31)
+ stem[0] = 0x42
+
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+ values[10] = common.HexToHash("0x0202").Bytes()
+ values[255] = common.HexToHash("0x0303").Bytes()
+
+ newNode, err := node.InsertValuesAtStem(stem, values[:], nil, 5)
+ if err != nil {
+ t.Fatalf("Failed to insert values: %v", err)
+ }
+
+ // Should create a StemNode
+ stemNode, ok := newNode.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected StemNode, got %T", newNode)
+ }
+
+ // Check the stem
+ if !bytes.Equal(stemNode.Stem, stem) {
+ t.Errorf("Stem mismatch: expected %x, got %x", stem, stemNode.Stem)
+ }
+
+ // Check the depth
+ if stemNode.depth != 5 {
+ t.Errorf("Depth mismatch: expected 5, got %d", stemNode.depth)
+ }
+
+ // Check the values
+ if !bytes.Equal(stemNode.Values[0], values[0]) {
+ t.Error("Value at index 0 mismatch")
+ }
+ if !bytes.Equal(stemNode.Values[10], values[10]) {
+ t.Error("Value at index 10 mismatch")
+ }
+ if !bytes.Equal(stemNode.Values[255], values[255]) {
+ t.Error("Value at index 255 mismatch")
+ }
+
+ // Check that values is the same slice (not a copy)
+ if &stemNode.Values[0] != &values[0] {
+ t.Error("Expected values to be the same slice reference")
+ }
+}
+
+// TestEmptyCollectNodes tests the CollectNodes method
+func TestEmptyCollectNodes(t *testing.T) {
+ node := Empty{}
+
+ var collected []BinaryNode
+ flushFn := func(path []byte, n BinaryNode) {
+ collected = append(collected, n)
+ }
+
+ err := node.CollectNodes([]byte{0, 1, 0}, flushFn)
+ if err != nil {
+ t.Fatalf("Unexpected error: %v", err)
+ }
+
+ // Should not collect anything for empty node
+ if len(collected) != 0 {
+ t.Errorf("Expected no collected nodes for empty, got %d", len(collected))
+ }
+}
+
+// TestEmptyToDot tests the toDot method
+func TestEmptyToDot(t *testing.T) {
+ node := Empty{}
+
+ dot := node.toDot("parent", "010")
+
+ // Should return empty string for empty node
+ if dot != "" {
+ t.Errorf("Expected empty string for empty node toDot, got %s", dot)
+ }
+}
+
+// TestEmptyGetHeight tests the GetHeight method
+func TestEmptyGetHeight(t *testing.T) {
+ node := Empty{}
+
+ height := node.GetHeight()
+
+ // Empty node should have height 0
+ if height != 0 {
+ t.Errorf("Expected height 0 for empty node, got %d", height)
+ }
+}
diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go
new file mode 100644
index 00000000000..dbc6a7a2443
--- /dev/null
+++ b/trie/bintrie/hashed_node_test.go
@@ -0,0 +1,174 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// TestHashedNodeHash tests the Hash method
+func TestHashedNodeHash(t *testing.T) {
+ hash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
+ node := HashedNode(hash)
+
+ // Hash should return the stored hash
+ if node.Hash() != hash {
+ t.Errorf("Hash mismatch: expected %x, got %x", hash, node.Hash())
+ }
+}
+
+// TestHashedNodeCopy tests the Copy method
+func TestHashedNodeCopy(t *testing.T) {
+ hash := common.HexToHash("0xabcdef")
+ node := HashedNode(hash)
+
+ copied := node.Copy()
+ copiedHash, ok := copied.(HashedNode)
+ if !ok {
+ t.Fatalf("Expected HashedNode, got %T", copied)
+ }
+
+ // Hash should be the same
+ if common.Hash(copiedHash) != hash {
+ t.Errorf("Hash mismatch after copy: expected %x, got %x", hash, copiedHash)
+ }
+
+ // But should be a different object
+ if &node == &copiedHash {
+ t.Error("Copy returned same object reference")
+ }
+}
+
+// TestHashedNodeInsert tests that Insert returns an error
+func TestHashedNodeInsert(t *testing.T) {
+ node := HashedNode(common.HexToHash("0x1234"))
+
+ key := make([]byte, 32)
+ value := make([]byte, 32)
+
+ _, err := node.Insert(key, value, nil)
+ if err == nil {
+ t.Fatal("Expected error for Insert on HashedNode")
+ }
+
+ if err.Error() != "insert not implemented for hashed node" {
+ t.Errorf("Unexpected error message: %v", err)
+ }
+}
+
+// TestHashedNodeGetValuesAtStem tests that GetValuesAtStem returns an error
+func TestHashedNodeGetValuesAtStem(t *testing.T) {
+ node := HashedNode(common.HexToHash("0x1234"))
+
+ stem := make([]byte, 31)
+ _, err := node.GetValuesAtStem(stem, nil)
+ if err == nil {
+ t.Fatal("Expected error for GetValuesAtStem on HashedNode")
+ }
+
+ if err.Error() != "attempted to get values from an unresolved node" {
+ t.Errorf("Unexpected error message: %v", err)
+ }
+}
+
+// TestHashedNodeInsertValuesAtStem tests that InsertValuesAtStem returns an error
+func TestHashedNodeInsertValuesAtStem(t *testing.T) {
+ node := HashedNode(common.HexToHash("0x1234"))
+
+ stem := make([]byte, 31)
+ values := make([][]byte, 256)
+
+ _, err := node.InsertValuesAtStem(stem, values, nil, 0)
+ if err == nil {
+ t.Fatal("Expected error for InsertValuesAtStem on HashedNode")
+ }
+
+ if err.Error() != "insertValuesAtStem not implemented for hashed node" {
+ t.Errorf("Unexpected error message: %v", err)
+ }
+}
+
+// TestHashedNodeGet tests that Get panics (as per implementation)
+func TestHashedNodeGet(t *testing.T) {
+ node := HashedNode(common.HexToHash("0x1234"))
+
+ defer func() {
+ if r := recover(); r == nil {
+ t.Error("Expected panic for Get on HashedNode")
+ }
+ }()
+
+ key := make([]byte, 32)
+ _, _ = node.Get(key, nil)
+}
+
+// TestHashedNodeCollectNodes tests that CollectNodes panics (as per implementation)
+func TestHashedNodeCollectNodes(t *testing.T) {
+ node := HashedNode(common.HexToHash("0x1234"))
+
+ defer func() {
+ if r := recover(); r == nil {
+ t.Error("Expected panic for CollectNodes on HashedNode")
+ }
+ }()
+
+ path := []byte{0, 1, 0}
+ node.CollectNodes(path, func([]byte, BinaryNode) {})
+}
+
+// TestHashedNodeGetHeight tests that GetHeight panics (as per implementation)
+func TestHashedNodeGetHeight(t *testing.T) {
+ node := HashedNode(common.HexToHash("0x1234"))
+
+ defer func() {
+ r := recover()
+ if r == nil {
+ t.Error("Expected panic for GetHeight on HashedNode")
+ }
+ // Check the panic message
+ if r != "tried to get the height of a hashed node, this is a bug" {
+ t.Errorf("Unexpected panic message: %v", r)
+ }
+ }()
+
+ _ = node.GetHeight()
+}
+
+// TestHashedNodeToDot tests the toDot method for visualization
+func TestHashedNodeToDot(t *testing.T) {
+ hash := common.HexToHash("0x1234")
+ node := HashedNode(hash)
+
+ dot := node.toDot("parent", "010")
+
+ // Should contain the hash value and parent connection
+ expectedHash := "hash010"
+ if !contains(dot, expectedHash) {
+ t.Errorf("Expected dot output to contain %s", expectedHash)
+ }
+
+ if !contains(dot, "parent -> hash010") {
+ t.Error("Expected dot output to contain parent connection")
+ }
+}
+
+// Helper function
+func contains(s, substr string) bool {
+ return len(s) >= len(substr) && s[:len(s)] != "" && len(substr) > 0
+}
diff --git a/trie/bintrie/internal_node_test.go b/trie/bintrie/internal_node_test.go
new file mode 100644
index 00000000000..142f1e95c1d
--- /dev/null
+++ b/trie/bintrie/internal_node_test.go
@@ -0,0 +1,458 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "bytes"
+ "errors"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// TestInternalNodeGet tests the Get method
+func TestInternalNodeGet(t *testing.T) {
+ // Create a simple tree structure
+ leftStem := make([]byte, 31)
+ rightStem := make([]byte, 31)
+ rightStem[0] = 0x80 // First bit is 1
+
+ var leftValues, rightValues [256][]byte
+ leftValues[0] = common.HexToHash("0x0101").Bytes()
+ rightValues[0] = common.HexToHash("0x0202").Bytes()
+
+ node := &InternalNode{
+ depth: 0,
+ Left: &StemNode{
+ Stem: leftStem,
+ Values: leftValues[:],
+ depth: 1,
+ },
+ Right: &StemNode{
+ Stem: rightStem,
+ Values: rightValues[:],
+ depth: 1,
+ },
+ }
+
+ // Get value from left subtree
+ leftKey := make([]byte, 32)
+ leftKey[31] = 0
+ value, err := node.Get(leftKey, nil)
+ if err != nil {
+ t.Fatalf("Failed to get left value: %v", err)
+ }
+ if !bytes.Equal(value, leftValues[0]) {
+ t.Errorf("Left value mismatch: expected %x, got %x", leftValues[0], value)
+ }
+
+ // Get value from right subtree
+ rightKey := make([]byte, 32)
+ rightKey[0] = 0x80
+ rightKey[31] = 0
+ value, err = node.Get(rightKey, nil)
+ if err != nil {
+ t.Fatalf("Failed to get right value: %v", err)
+ }
+ if !bytes.Equal(value, rightValues[0]) {
+ t.Errorf("Right value mismatch: expected %x, got %x", rightValues[0], value)
+ }
+}
+
+// TestInternalNodeGetWithResolver tests Get with HashedNode resolution
+func TestInternalNodeGetWithResolver(t *testing.T) {
+ // Create an internal node with a hashed child
+ hashedChild := HashedNode(common.HexToHash("0x1234"))
+
+ node := &InternalNode{
+ depth: 0,
+ Left: hashedChild,
+ Right: Empty{},
+ }
+
+ // Mock resolver that returns a stem node
+ resolver := func(path []byte, hash common.Hash) ([]byte, error) {
+ if hash == common.Hash(hashedChild) {
+ stem := make([]byte, 31)
+ var values [256][]byte
+ values[5] = common.HexToHash("0xabcd").Bytes()
+ stemNode := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 1,
+ }
+ return SerializeNode(stemNode), nil
+ }
+ return nil, errors.New("node not found")
+ }
+
+ // Get value through the hashed node
+ key := make([]byte, 32)
+ key[31] = 5
+ value, err := node.Get(key, resolver)
+ if err != nil {
+ t.Fatalf("Failed to get value: %v", err)
+ }
+
+ expectedValue := common.HexToHash("0xabcd").Bytes()
+ if !bytes.Equal(value, expectedValue) {
+ t.Errorf("Value mismatch: expected %x, got %x", expectedValue, value)
+ }
+}
+
+// TestInternalNodeInsert tests the Insert method
+func TestInternalNodeInsert(t *testing.T) {
+ // Start with an internal node with empty children
+ node := &InternalNode{
+ depth: 0,
+ Left: Empty{},
+ Right: Empty{},
+ }
+
+ // Insert a value into the left subtree
+ leftKey := make([]byte, 32)
+ leftKey[31] = 10
+ leftValue := common.HexToHash("0x0101").Bytes()
+
+ newNode, err := node.Insert(leftKey, leftValue, nil)
+ if err != nil {
+ t.Fatalf("Failed to insert: %v", err)
+ }
+
+ internalNode, ok := newNode.(*InternalNode)
+ if !ok {
+ t.Fatalf("Expected InternalNode, got %T", newNode)
+ }
+
+ // Check that left child is now a StemNode
+ leftStem, ok := internalNode.Left.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
+ }
+
+ // Check the inserted value
+ if !bytes.Equal(leftStem.Values[10], leftValue) {
+ t.Errorf("Value mismatch: expected %x, got %x", leftValue, leftStem.Values[10])
+ }
+
+ // Right child should still be Empty
+ _, ok = internalNode.Right.(Empty)
+ if !ok {
+ t.Errorf("Expected right child to remain Empty, got %T", internalNode.Right)
+ }
+}
+
+// TestInternalNodeCopy tests the Copy method
+func TestInternalNodeCopy(t *testing.T) {
+ // Create an internal node with stem children
+ leftStem := &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 1,
+ }
+ leftStem.Values[0] = common.HexToHash("0x0101").Bytes()
+
+ rightStem := &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 1,
+ }
+ rightStem.Stem[0] = 0x80
+ rightStem.Values[0] = common.HexToHash("0x0202").Bytes()
+
+ node := &InternalNode{
+ depth: 0,
+ Left: leftStem,
+ Right: rightStem,
+ }
+
+ // Create a copy
+ copied := node.Copy()
+ copiedInternal, ok := copied.(*InternalNode)
+ if !ok {
+ t.Fatalf("Expected InternalNode, got %T", copied)
+ }
+
+ // Check depth
+ if copiedInternal.depth != node.depth {
+ t.Errorf("Depth mismatch: expected %d, got %d", node.depth, copiedInternal.depth)
+ }
+
+ // Check that children are copied
+ copiedLeft, ok := copiedInternal.Left.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected left child to be StemNode, got %T", copiedInternal.Left)
+ }
+
+ copiedRight, ok := copiedInternal.Right.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected right child to be StemNode, got %T", copiedInternal.Right)
+ }
+
+ // Verify deep copy (children should be different objects)
+ if copiedLeft == leftStem {
+ t.Error("Left child not properly copied")
+ }
+ if copiedRight == rightStem {
+ t.Error("Right child not properly copied")
+ }
+
+ // But values should be equal
+ if !bytes.Equal(copiedLeft.Values[0], leftStem.Values[0]) {
+ t.Error("Left child value mismatch after copy")
+ }
+ if !bytes.Equal(copiedRight.Values[0], rightStem.Values[0]) {
+ t.Error("Right child value mismatch after copy")
+ }
+}
+
+// TestInternalNodeHash tests the Hash method
+func TestInternalNodeHash(t *testing.T) {
+ // Create an internal node
+ node := &InternalNode{
+ depth: 0,
+ Left: HashedNode(common.HexToHash("0x1111")),
+ Right: HashedNode(common.HexToHash("0x2222")),
+ }
+
+ hash1 := node.Hash()
+
+ // Hash should be deterministic
+ hash2 := node.Hash()
+ if hash1 != hash2 {
+ t.Errorf("Hash not deterministic: %x != %x", hash1, hash2)
+ }
+
+ // Changing a child should change the hash
+ node.Left = HashedNode(common.HexToHash("0x3333"))
+ hash3 := node.Hash()
+ if hash1 == hash3 {
+ t.Error("Hash didn't change after modifying left child")
+ }
+
+ // Test with nil children (should use zero hash)
+ nodeWithNil := &InternalNode{
+ depth: 0,
+ Left: nil,
+ Right: HashedNode(common.HexToHash("0x4444")),
+ }
+ hashWithNil := nodeWithNil.Hash()
+ if hashWithNil == (common.Hash{}) {
+ t.Error("Hash shouldn't be zero even with nil child")
+ }
+}
+
+// TestInternalNodeGetValuesAtStem tests GetValuesAtStem method
+func TestInternalNodeGetValuesAtStem(t *testing.T) {
+ // Create a tree with values at different stems
+ leftStem := make([]byte, 31)
+ rightStem := make([]byte, 31)
+ rightStem[0] = 0x80
+
+ var leftValues, rightValues [256][]byte
+ leftValues[0] = common.HexToHash("0x0101").Bytes()
+ leftValues[10] = common.HexToHash("0x0102").Bytes()
+ rightValues[0] = common.HexToHash("0x0201").Bytes()
+ rightValues[20] = common.HexToHash("0x0202").Bytes()
+
+ node := &InternalNode{
+ depth: 0,
+ Left: &StemNode{
+ Stem: leftStem,
+ Values: leftValues[:],
+ depth: 1,
+ },
+ Right: &StemNode{
+ Stem: rightStem,
+ Values: rightValues[:],
+ depth: 1,
+ },
+ }
+
+ // Get values from left stem
+ values, err := node.GetValuesAtStem(leftStem, nil)
+ if err != nil {
+ t.Fatalf("Failed to get left values: %v", err)
+ }
+ if !bytes.Equal(values[0], leftValues[0]) {
+ t.Error("Left value at index 0 mismatch")
+ }
+ if !bytes.Equal(values[10], leftValues[10]) {
+ t.Error("Left value at index 10 mismatch")
+ }
+
+ // Get values from right stem
+ values, err = node.GetValuesAtStem(rightStem, nil)
+ if err != nil {
+ t.Fatalf("Failed to get right values: %v", err)
+ }
+ if !bytes.Equal(values[0], rightValues[0]) {
+ t.Error("Right value at index 0 mismatch")
+ }
+ if !bytes.Equal(values[20], rightValues[20]) {
+ t.Error("Right value at index 20 mismatch")
+ }
+}
+
+// TestInternalNodeInsertValuesAtStem tests InsertValuesAtStem method
+func TestInternalNodeInsertValuesAtStem(t *testing.T) {
+ // Start with an internal node with empty children
+ node := &InternalNode{
+ depth: 0,
+ Left: Empty{},
+ Right: Empty{},
+ }
+
+ // Insert values at a stem in the left subtree
+ stem := make([]byte, 31)
+ var values [256][]byte
+ values[5] = common.HexToHash("0x0505").Bytes()
+ values[10] = common.HexToHash("0x1010").Bytes()
+
+ newNode, err := node.InsertValuesAtStem(stem, values[:], nil, 0)
+ if err != nil {
+ t.Fatalf("Failed to insert values: %v", err)
+ }
+
+ internalNode, ok := newNode.(*InternalNode)
+ if !ok {
+ t.Fatalf("Expected InternalNode, got %T", newNode)
+ }
+
+ // Check that left child is now a StemNode with the values
+ leftStem, ok := internalNode.Left.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
+ }
+
+ if !bytes.Equal(leftStem.Values[5], values[5]) {
+ t.Error("Value at index 5 mismatch")
+ }
+ if !bytes.Equal(leftStem.Values[10], values[10]) {
+ t.Error("Value at index 10 mismatch")
+ }
+}
+
+// TestInternalNodeCollectNodes tests CollectNodes method
+func TestInternalNodeCollectNodes(t *testing.T) {
+ // Create an internal node with two stem children
+ leftStem := &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 1,
+ }
+
+ rightStem := &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 1,
+ }
+ rightStem.Stem[0] = 0x80
+
+ node := &InternalNode{
+ depth: 0,
+ Left: leftStem,
+ Right: rightStem,
+ }
+
+ var collectedPaths [][]byte
+ var collectedNodes []BinaryNode
+
+ flushFn := func(path []byte, n BinaryNode) {
+ pathCopy := make([]byte, len(path))
+ copy(pathCopy, path)
+ collectedPaths = append(collectedPaths, pathCopy)
+ collectedNodes = append(collectedNodes, n)
+ }
+
+ err := node.CollectNodes([]byte{1}, flushFn)
+ if err != nil {
+ t.Fatalf("Failed to collect nodes: %v", err)
+ }
+
+ // Should have collected 3 nodes: left stem, right stem, and the internal node itself
+ if len(collectedNodes) != 3 {
+ t.Errorf("Expected 3 collected nodes, got %d", len(collectedNodes))
+ }
+
+ // Check paths
+ expectedPaths := [][]byte{
+ {1, 0}, // left child
+ {1, 1}, // right child
+ {1}, // internal node itself
+ }
+
+ for i, expectedPath := range expectedPaths {
+ if !bytes.Equal(collectedPaths[i], expectedPath) {
+ t.Errorf("Path %d mismatch: expected %v, got %v", i, expectedPath, collectedPaths[i])
+ }
+ }
+}
+
+// TestInternalNodeGetHeight tests GetHeight method
+func TestInternalNodeGetHeight(t *testing.T) {
+ // Create a tree with different heights
+ // Left subtree: depth 2 (internal -> stem)
+ // Right subtree: depth 1 (stem)
+ leftInternal := &InternalNode{
+ depth: 1,
+ Left: &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 2,
+ },
+ Right: Empty{},
+ }
+
+ rightStem := &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 1,
+ }
+
+ node := &InternalNode{
+ depth: 0,
+ Left: leftInternal,
+ Right: rightStem,
+ }
+
+ height := node.GetHeight()
+ // Height should be max(left height, right height) + 1
+ // Left height: 2, Right height: 1, so total: 3
+ if height != 3 {
+ t.Errorf("Expected height 3, got %d", height)
+ }
+}
+
+// TestInternalNodeDepthTooLarge tests handling of excessive depth
+func TestInternalNodeDepthTooLarge(t *testing.T) {
+ // Create an internal node at max depth
+ node := &InternalNode{
+ depth: 31*8 + 1,
+ Left: Empty{},
+ Right: Empty{},
+ }
+
+ stem := make([]byte, 31)
+ _, err := node.GetValuesAtStem(stem, nil)
+ if err == nil {
+ t.Fatal("Expected error for excessive depth")
+ }
+ if err.Error() != "node too deep" {
+ t.Errorf("Expected 'node too deep' error, got: %v", err)
+ }
+}
diff --git a/trie/binary_iterator.go b/trie/bintrie/iterator.go
similarity index 86%
rename from trie/binary_iterator.go
rename to trie/bintrie/iterator.go
index e4799d207ab..94ce3895df3 100644
--- a/trie/binary_iterator.go
+++ b/trie/bintrie/iterator.go
@@ -14,31 +14,35 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package trie
+package bintrie
import (
+ "errors"
+
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/trie/bintrie"
+ "github.com/ethereum/go-ethereum/trie"
)
+var errIteratorEnd = errors.New("end of iteration")
+
type binaryNodeIteratorState struct {
- Node bintrie.BinaryNode
+ Node BinaryNode
Index int
}
type binaryNodeIterator struct {
trie *BinaryTrie
- current bintrie.BinaryNode
+ current BinaryNode
lastErr error
stack []binaryNodeIteratorState
}
-func newBinaryNodeIterator(trie *BinaryTrie, _ []byte) (NodeIterator, error) {
- if trie.Hash() == zero {
- return new(nodeIterator), nil
+func newBinaryNodeIterator(t *BinaryTrie, _ []byte) (trie.NodeIterator, error) {
+ if t.Hash() == zero {
+ return &binaryNodeIterator{trie: t, lastErr: errIteratorEnd}, nil
}
- it := &binaryNodeIterator{trie: trie, current: trie.root}
+ it := &binaryNodeIterator{trie: t, current: t.root}
// it.err = it.seek(start)
return it, nil
}
@@ -59,13 +63,13 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
}
switch node := it.current.(type) {
- case *bintrie.InternalNode:
+ case *InternalNode:
// index: 0 = nothing visited, 1=left visited, 2=right visited
context := &it.stack[len(it.stack)-1]
// recurse into both children
if context.Index == 0 {
- if _, isempty := node.Left.(bintrie.Empty); node.Left != nil && !isempty {
+ if _, isempty := node.Left.(Empty); node.Left != nil && !isempty {
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
it.current = node.Left
return it.Next(descend)
@@ -75,7 +79,7 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
}
if context.Index == 1 {
- if _, isempty := node.Right.(bintrie.Empty); node.Right != nil && !isempty {
+ if _, isempty := node.Right.(Empty); node.Right != nil && !isempty {
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
it.current = node.Right
return it.Next(descend)
@@ -94,7 +98,7 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
it.current = it.stack[len(it.stack)-1].Node
it.stack[len(it.stack)-1].Index++
return it.Next(descend)
- case *bintrie.StemNode:
+ case *StemNode:
// Look for the next non-empty value
for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
if node.Values[i] != nil {
@@ -108,13 +112,13 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
it.current = it.stack[len(it.stack)-1].Node
it.stack[len(it.stack)-1].Index++
return it.Next(descend)
- case bintrie.HashedNode:
+ case HashedNode:
// resolve the node
data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
if err != nil {
panic(err)
}
- it.current, err = bintrie.DeserializeNode(data, len(it.stack)-1)
+ it.current, err = DeserializeNode(data, len(it.stack)-1)
if err != nil {
panic(err)
}
@@ -123,12 +127,12 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
it.stack[len(it.stack)-1].Node = it.current
parent := &it.stack[len(it.stack)-2]
if parent.Index == 0 {
- parent.Node.(*bintrie.InternalNode).Left = it.current
+ parent.Node.(*InternalNode).Left = it.current
} else {
- parent.Node.(*bintrie.InternalNode).Right = it.current
+ parent.Node.(*InternalNode).Right = it.current
}
return it.Next(descend)
- case bintrie.Empty:
+ case Empty:
// do nothing
return false
default:
@@ -179,7 +183,7 @@ func (it *binaryNodeIterator) NodeBlob() []byte {
// Leaf returns true iff the current node is a leaf node.
func (it *binaryNodeIterator) Leaf() bool {
- _, ok := it.current.(*bintrie.StemNode)
+ _, ok := it.current.(*StemNode)
return ok
}
@@ -187,7 +191,7 @@ func (it *binaryNodeIterator) Leaf() bool {
// positioned at a leaf. Callers must not retain references to the value after
// calling Next.
func (it *binaryNodeIterator) LeafKey() []byte {
- leaf, ok := it.current.(*bintrie.StemNode)
+ leaf, ok := it.current.(*StemNode)
if !ok {
panic("Leaf() called on an binary node iterator not at a leaf location")
}
@@ -199,7 +203,7 @@ func (it *binaryNodeIterator) LeafKey() []byte {
// is not positioned at a leaf. Callers must not retain references to the value
// after calling Next.
func (it *binaryNodeIterator) LeafBlob() []byte {
- leaf, ok := it.current.(*bintrie.StemNode)
+ leaf, ok := it.current.(*StemNode)
if !ok {
panic("LeafBlob() called on an binary node iterator not at a leaf location")
}
@@ -211,7 +215,7 @@ func (it *binaryNodeIterator) LeafBlob() []byte {
// iterator is not positioned at a leaf. Callers must not retain references
// to the value after calling Next.
func (it *binaryNodeIterator) LeafProof() [][]byte {
- _, ok := it.current.(*bintrie.StemNode)
+ _, ok := it.current.(*StemNode)
if !ok {
panic("LeafProof() called on an binary node iterator not at a leaf location")
}
@@ -231,6 +235,6 @@ func (it *binaryNodeIterator) LeafProof() [][]byte {
// Before adding a similar mechanism to any other place in Geth, consider
// making trie.Database an interface and wrapping at that level. It's a huge
// refactor, but it could be worth it if another occurrence arises.
-func (it *binaryNodeIterator) AddResolver(NodeResolver) {
+func (it *binaryNodeIterator) AddResolver(trie.NodeResolver) {
// Not implemented, but should not panic
}
diff --git a/trie/binary_iterator_test.go b/trie/bintrie/iterator_test.go
similarity index 80%
rename from trie/binary_iterator_test.go
rename to trie/bintrie/iterator_test.go
index 01a4373cd24..8773e9e0c54 100644
--- a/trie/binary_iterator_test.go
+++ b/trie/bintrie/iterator_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package trie
+package bintrie
import (
"testing"
@@ -22,9 +22,23 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/triedb"
+ "github.com/ethereum/go-ethereum/triedb/hashdb"
+ "github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256"
)
+func newTestDatabase(diskdb ethdb.Database, scheme string) *triedb.Database {
+ config := &triedb.Config{Preimages: true}
+ if scheme == rawdb.HashScheme {
+ config.HashDB = &hashdb.Config{CleanCacheSize: 0}
+ } else {
+ config.PathDB = &pathdb.Config{TrieCleanSize: 0, StateCleanSize: 0}
+ }
+ return triedb.NewDatabase(diskdb, config)
+}
+
func TestBinaryIterator(t *testing.T) {
trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme))
if err != nil {
diff --git a/trie/bintrie/stem_node_test.go b/trie/bintrie/stem_node_test.go
new file mode 100644
index 00000000000..47dda38f592
--- /dev/null
+++ b/trie/bintrie/stem_node_test.go
@@ -0,0 +1,373 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// TestStemNodeInsertSameStem tests inserting values with the same stem
+func TestStemNodeInsertSameStem(t *testing.T) {
+ stem := make([]byte, 31)
+ for i := range stem {
+ stem[i] = byte(i)
+ }
+
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 0,
+ }
+
+ // Insert another value with the same stem but different last byte
+ key := make([]byte, 32)
+ copy(key[:31], stem)
+ key[31] = 10
+ value := common.HexToHash("0x0202").Bytes()
+
+ newNode, err := node.Insert(key, value, nil)
+ if err != nil {
+ t.Fatalf("Failed to insert: %v", err)
+ }
+
+ // Should still be a StemNode
+ stemNode, ok := newNode.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected StemNode, got %T", newNode)
+ }
+
+ // Check that both values are present
+ if !bytes.Equal(stemNode.Values[0], values[0]) {
+ t.Errorf("Value at index 0 mismatch")
+ }
+ if !bytes.Equal(stemNode.Values[10], value) {
+ t.Errorf("Value at index 10 mismatch")
+ }
+}
+
+// TestStemNodeInsertDifferentStem tests inserting values with different stems
+func TestStemNodeInsertDifferentStem(t *testing.T) {
+ stem1 := make([]byte, 31)
+ for i := range stem1 {
+ stem1[i] = 0x00
+ }
+
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+
+ node := &StemNode{
+ Stem: stem1,
+ Values: values[:],
+ depth: 0,
+ }
+
+ // Insert with a different stem (first bit different)
+ key := make([]byte, 32)
+ key[0] = 0x80 // First bit is 1 instead of 0
+ value := common.HexToHash("0x0202").Bytes()
+
+ newNode, err := node.Insert(key, value, nil)
+ if err != nil {
+ t.Fatalf("Failed to insert: %v", err)
+ }
+
+ // Should now be an InternalNode
+ internalNode, ok := newNode.(*InternalNode)
+ if !ok {
+ t.Fatalf("Expected InternalNode, got %T", newNode)
+ }
+
+ // Check depth
+ if internalNode.depth != 0 {
+ t.Errorf("Expected depth 0, got %d", internalNode.depth)
+ }
+
+ // Original stem should be on the left (bit 0)
+ leftStem, ok := internalNode.Left.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
+ }
+ if !bytes.Equal(leftStem.Stem, stem1) {
+ t.Errorf("Left stem mismatch")
+ }
+
+ // New stem should be on the right (bit 1)
+ rightStem, ok := internalNode.Right.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected right child to be StemNode, got %T", internalNode.Right)
+ }
+ if !bytes.Equal(rightStem.Stem, key[:31]) {
+ t.Errorf("Right stem mismatch")
+ }
+}
+
+// TestStemNodeInsertInvalidValueLength tests inserting value with invalid length
+func TestStemNodeInsertInvalidValueLength(t *testing.T) {
+ stem := make([]byte, 31)
+ var values [256][]byte
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 0,
+ }
+
+ // Try to insert value with wrong length
+ key := make([]byte, 32)
+ copy(key[:31], stem)
+ invalidValue := []byte{1, 2, 3} // Not 32 bytes
+
+ _, err := node.Insert(key, invalidValue, nil)
+ if err == nil {
+ t.Fatal("Expected error for invalid value length")
+ }
+
+ if err.Error() != "invalid insertion: value length" {
+ t.Errorf("Expected 'invalid insertion: value length' error, got: %v", err)
+ }
+}
+
+// TestStemNodeCopy tests the Copy method
+func TestStemNodeCopy(t *testing.T) {
+ stem := make([]byte, 31)
+ for i := range stem {
+ stem[i] = byte(i)
+ }
+
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+ values[255] = common.HexToHash("0x0202").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 10,
+ }
+
+ // Create a copy
+ copied := node.Copy()
+ copiedStem, ok := copied.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected StemNode, got %T", copied)
+ }
+
+ // Check that values are equal but not the same slice
+ if !bytes.Equal(copiedStem.Stem, node.Stem) {
+ t.Errorf("Stem mismatch after copy")
+ }
+ if &copiedStem.Stem[0] == &node.Stem[0] {
+ t.Error("Stem slice not properly cloned")
+ }
+
+ // Check values
+ if !bytes.Equal(copiedStem.Values[0], node.Values[0]) {
+ t.Errorf("Value at index 0 mismatch after copy")
+ }
+ if !bytes.Equal(copiedStem.Values[255], node.Values[255]) {
+ t.Errorf("Value at index 255 mismatch after copy")
+ }
+
+ // Check that value slices are cloned
+ if copiedStem.Values[0] != nil && &copiedStem.Values[0][0] == &node.Values[0][0] {
+ t.Error("Value slice not properly cloned")
+ }
+
+ // Check depth
+ if copiedStem.depth != node.depth {
+ t.Errorf("Depth mismatch: expected %d, got %d", node.depth, copiedStem.depth)
+ }
+}
+
+// TestStemNodeHash tests the Hash method
+func TestStemNodeHash(t *testing.T) {
+ stem := make([]byte, 31)
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 0,
+ }
+
+ hash1 := node.Hash()
+
+ // Hash should be deterministic
+ hash2 := node.Hash()
+ if hash1 != hash2 {
+ t.Errorf("Hash not deterministic: %x != %x", hash1, hash2)
+ }
+
+ // Changing a value should change the hash
+ node.Values[1] = common.HexToHash("0x0202").Bytes()
+ hash3 := node.Hash()
+ if hash1 == hash3 {
+ t.Error("Hash didn't change after modifying values")
+ }
+}
+
+// TestStemNodeGetValuesAtStem tests GetValuesAtStem method
+func TestStemNodeGetValuesAtStem(t *testing.T) {
+ stem := make([]byte, 31)
+ for i := range stem {
+ stem[i] = byte(i)
+ }
+
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+ values[10] = common.HexToHash("0x0202").Bytes()
+ values[255] = common.HexToHash("0x0303").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 0,
+ }
+
+ // GetValuesAtStem with matching stem
+ retrievedValues, err := node.GetValuesAtStem(stem, nil)
+ if err != nil {
+ t.Fatalf("Failed to get values: %v", err)
+ }
+
+ // Check that all values match
+ for i := 0; i < 256; i++ {
+ if !bytes.Equal(retrievedValues[i], values[i]) {
+ t.Errorf("Value mismatch at index %d", i)
+ }
+ }
+
+ // GetValuesAtStem with different stem also returns the same values
+ // (implementation ignores the stem parameter)
+ differentStem := make([]byte, 31)
+ differentStem[0] = 0xFF
+
+ retrievedValues2, err := node.GetValuesAtStem(differentStem, nil)
+ if err != nil {
+ t.Fatalf("Failed to get values with different stem: %v", err)
+ }
+
+ // Should still return the same values (stem is ignored)
+ for i := 0; i < 256; i++ {
+ if !bytes.Equal(retrievedValues2[i], values[i]) {
+ t.Errorf("Value mismatch at index %d with different stem", i)
+ }
+ }
+}
+
+// TestStemNodeInsertValuesAtStem tests InsertValuesAtStem method
+func TestStemNodeInsertValuesAtStem(t *testing.T) {
+ stem := make([]byte, 31)
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 0,
+ }
+
+ // Insert new values at the same stem
+ var newValues [256][]byte
+ newValues[1] = common.HexToHash("0x0202").Bytes()
+ newValues[2] = common.HexToHash("0x0303").Bytes()
+
+ newNode, err := node.InsertValuesAtStem(stem, newValues[:], nil, 0)
+ if err != nil {
+ t.Fatalf("Failed to insert values: %v", err)
+ }
+
+ stemNode, ok := newNode.(*StemNode)
+ if !ok {
+ t.Fatalf("Expected StemNode, got %T", newNode)
+ }
+
+ // Check that all values are present
+ if !bytes.Equal(stemNode.Values[0], values[0]) {
+ t.Error("Original value at index 0 missing")
+ }
+ if !bytes.Equal(stemNode.Values[1], newValues[1]) {
+ t.Error("New value at index 1 missing")
+ }
+ if !bytes.Equal(stemNode.Values[2], newValues[2]) {
+ t.Error("New value at index 2 missing")
+ }
+}
+
+// TestStemNodeGetHeight tests GetHeight method
+func TestStemNodeGetHeight(t *testing.T) {
+ node := &StemNode{
+ Stem: make([]byte, 31),
+ Values: make([][]byte, 256),
+ depth: 0,
+ }
+
+ height := node.GetHeight()
+ if height != 1 {
+ t.Errorf("Expected height 1, got %d", height)
+ }
+}
+
+// TestStemNodeCollectNodes tests CollectNodes method
+func TestStemNodeCollectNodes(t *testing.T) {
+ stem := make([]byte, 31)
+ var values [256][]byte
+ values[0] = common.HexToHash("0x0101").Bytes()
+
+ node := &StemNode{
+ Stem: stem,
+ Values: values[:],
+ depth: 0,
+ }
+
+ var collectedPaths [][]byte
+ var collectedNodes []BinaryNode
+
+ flushFn := func(path []byte, n BinaryNode) {
+ // Make a copy of the path
+ pathCopy := make([]byte, len(path))
+ copy(pathCopy, path)
+ collectedPaths = append(collectedPaths, pathCopy)
+ collectedNodes = append(collectedNodes, n)
+ }
+
+ err := node.CollectNodes([]byte{0, 1, 0}, flushFn)
+ if err != nil {
+ t.Fatalf("Failed to collect nodes: %v", err)
+ }
+
+ // Should have collected one node (itself)
+ if len(collectedNodes) != 1 {
+ t.Errorf("Expected 1 collected node, got %d", len(collectedNodes))
+ }
+
+ // Check that the collected node is the same
+ if collectedNodes[0] != node {
+ t.Error("Collected node doesn't match original")
+ }
+
+ // Check the path
+ if !bytes.Equal(collectedPaths[0], []byte{0, 1, 0}) {
+ t.Errorf("Path mismatch: expected [0, 1, 0], got %v", collectedPaths[0])
+ }
+}
diff --git a/trie/binary.go b/trie/bintrie/trie.go
similarity index 74%
rename from trie/binary.go
rename to trie/bintrie/trie.go
index 2cad0999c7f..19543bf45cb 100644
--- a/trie/binary.go
+++ b/trie/bintrie/trie.go
@@ -14,58 +14,58 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package trie
+package bintrie
import (
"bytes"
"encoding/binary"
+ "errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/trie/bintrie"
+ "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb/database"
"github.com/holiman/uint256"
)
-// zero is the zero value for a 32-byte array.
-var zero [32]byte
+var errInvalidRootType = errors.New("invalid root type")
// NewBinaryNode creates a new empty binary trie
-func NewBinaryNode() bintrie.BinaryNode {
- return bintrie.Empty{}
+func NewBinaryNode() BinaryNode {
+ return Empty{}
}
// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
// interface so that Verkle trees can be reused verbatim.
type BinaryTrie struct {
- root bintrie.BinaryNode
- reader *trieReader
+ root BinaryNode
+ reader *trie.TrieReader
}
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
-func (trie *BinaryTrie) ToDot() string {
- trie.root.Hash()
- return bintrie.ToDot(trie.root)
+func (t *BinaryTrie) ToDot() string {
+ t.root.Hash()
+ return ToDot(t.root)
}
// NewBinaryTrie creates a new binary trie.
func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) {
- reader, err := newTrieReader(root, common.Hash{}, db)
+ reader, err := trie.NewTrieReader(root, common.Hash{}, db)
if err != nil {
return nil, err
}
// Parse the root verkle node if it's not empty.
node := NewBinaryNode()
if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
- blob, err := reader.node(nil, common.Hash{})
+ blob, err := reader.Node(nil, common.Hash{})
if err != nil {
return nil, err
}
- node, err = bintrie.DeserializeNode(blob, 0)
+ node, err = DeserializeNode(blob, 0)
if err != nil {
return nil, err
}
@@ -77,48 +77,48 @@ func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, err
}
// FlatdbNodeResolver is a node resolver that reads nodes from the flatdb.
-func (trie *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
+func (t *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
// empty nodes will be serialized as common.Hash{}, so capture
// this special use case.
if hash == (common.Hash{}) {
return nil, nil // empty node
}
- return trie.reader.node(path, hash)
+ return t.reader.Node(path, hash)
}
// GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value.
-func (trie *BinaryTrie) GetKey(key []byte) []byte {
+func (t *BinaryTrie) GetKey(key []byte) []byte {
return key
}
// Get returns the value for key stored in the trie. The value bytes must
// not be modified by the caller. If a node was not found in the database, a
// trie.MissingNodeError is returned.
-func (trie *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
- return trie.root.Get(utils.GetBinaryTreeKey(addr, key), trie.FlatdbNodeResolver)
+func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
+ return t.root.Get(utils.GetBinaryTreeKey(addr, key), t.FlatdbNodeResolver)
}
// GetWithHashedKey returns the value, assuming that the key has already
// been hashed.
-func (trie *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
- return trie.root.Get(key, trie.FlatdbNodeResolver)
+func (t *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
+ return t.root.Get(key, t.FlatdbNodeResolver)
}
// GetAccount returns the account information for the given address.
-func (trie *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
+func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
acc := &types.StateAccount{}
versionkey := utils.GetBinaryTreeKey(addr, zero[:])
var (
values [][]byte
err error
)
- switch r := trie.root.(type) {
- case *bintrie.InternalNode:
- values, err = r.GetValuesAtStem(versionkey[:31], trie.FlatdbNodeResolver)
- case *bintrie.StemNode:
+ switch r := t.root.(type) {
+ case *InternalNode:
+ values, err = r.GetValuesAtStem(versionkey[:31], t.FlatdbNodeResolver)
+ case *StemNode:
values = r.Values
- case bintrie.Empty:
+ case Empty:
return nil, nil
default:
// This will cover HashedNode but that should be fine since the
@@ -160,11 +160,11 @@ func (trie *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, er
}
// UpdateAccount updates the account information for the given address.
-func (trie *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
+func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
var (
err error
basicData [32]byte
- values = make([][]byte, bintrie.NodeWidth)
+ values = make([][]byte, NodeWidth)
stem = utils.GetBinaryTreeKey(addr, zero[:])
)
@@ -183,14 +183,14 @@ func (trie *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccou
values[utils.BasicDataLeafKey] = basicData[:]
values[utils.CodeHashLeafKey] = acc.CodeHash[:]
- trie.root, err = trie.root.InsertValuesAtStem(stem, values, trie.FlatdbNodeResolver, 0)
+ t.root, err = t.root.InsertValuesAtStem(stem, values, t.FlatdbNodeResolver, 0)
return err
}
// UpdateStem updates the values for the given stem key.
-func (trie *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
+func (t *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
var err error
- trie.root, err = trie.root.InsertValuesAtStem(key, values, trie.FlatdbNodeResolver, 0)
+ t.root, err = t.root.InsertValuesAtStem(key, values, t.FlatdbNodeResolver, 0)
return err
}
@@ -198,7 +198,7 @@ func (trie *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
// existing value is deleted from the trie. The value bytes must not be modified
// by the caller while they are stored in the trie. If a node was not found in the
// database, a trie.MissingNodeError is returned.
-func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
+func (t *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
k := utils.GetBinaryTreeKeyStorageSlot(address, key)
var v [32]byte
if len(value) >= 32 {
@@ -206,46 +206,46 @@ func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte)
} else {
copy(v[32-len(value):], value[:])
}
- root, err := trie.root.Insert(k, v[:], trie.FlatdbNodeResolver)
+ root, err := t.root.Insert(k, v[:], t.FlatdbNodeResolver)
if err != nil {
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
}
- trie.root = root
+ t.root = root
return nil
}
// DeleteAccount is a no-op as it is disabled in stateless.
-func (trie *BinaryTrie) DeleteAccount(addr common.Address) error {
+func (t *BinaryTrie) DeleteAccount(addr common.Address) error {
return nil
}
// Delete removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
-func (trie *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
+func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
k := utils.GetBinaryTreeKey(addr, key)
var zero [32]byte
- root, err := trie.root.Insert(k, zero[:], trie.FlatdbNodeResolver)
+ root, err := t.root.Insert(k, zero[:], t.FlatdbNodeResolver)
if err != nil {
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
}
- trie.root = root
+ t.root = root
return nil
}
// Hash returns the root hash of the trie. It does not write to the database and
// can be used even if the trie doesn't have one.
-func (trie *BinaryTrie) Hash() common.Hash {
- return trie.root.Hash()
+func (t *BinaryTrie) Hash() common.Hash {
+ return t.root.Hash()
}
// Commit writes all nodes to the trie's memory database, tracking the internal
// and external (for account tries) references.
-func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
- root := trie.root.(*bintrie.InternalNode)
+func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
+ root := t.root.(*InternalNode)
nodeset := trienode.NewNodeSet(common.Hash{})
- err := root.CollectNodes(nil, func(path []byte, node bintrie.BinaryNode) {
- serialized := bintrie.SerializeNode(node)
+ err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
+ serialized := SerializeNode(node)
nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
})
if err != nil {
@@ -253,13 +253,13 @@ func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
}
// Serialize root commitment form
- return trie.Hash(), nodeset, nil
+ return t.Hash(), nodeset, nil
}
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key.
-func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
- return newBinaryNodeIterator(trie, nil)
+func (t *BinaryTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) {
+ return newBinaryNodeIterator(t, nil)
}
// Prove constructs a Merkle proof for key. The result contains all encoded nodes
@@ -269,20 +269,20 @@ func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
// If the trie does not contain a value for key, the returned proof contains all
// nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key.
-func (trie *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
+func (t *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
panic("not implemented")
}
// Copy creates a deep copy of the trie.
-func (trie *BinaryTrie) Copy() *BinaryTrie {
+func (t *BinaryTrie) Copy() *BinaryTrie {
return &BinaryTrie{
- root: trie.root.Copy(),
- reader: trie.reader,
+ root: t.root.Copy(),
+ reader: t.reader,
}
}
// IsVerkle returns true if the trie is a Verkle tree.
-func (trie *BinaryTrie) IsVerkle() bool {
+func (t *BinaryTrie) IsVerkle() bool {
// TODO @gballet This is technically NOT a verkle tree, but it has the same
// behavior and basic structure, so for all intents and purposes, it can be
// treated as such. Rename this when verkle gets removed.
@@ -290,9 +290,9 @@ func (trie *BinaryTrie) IsVerkle() bool {
}
// Note: the basic data leaf needs to have been previously created for this to work
-func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
+func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
var (
- chunks = ChunkifyCode(code)
+ chunks = trie.ChunkifyCode(code)
values [][]byte
key []byte
err error
@@ -300,7 +300,7 @@ func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.
for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
groupOffset := (chunknr + 128) % 256
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
- values = make([][]byte, bintrie.NodeWidth)
+ values = make([][]byte, NodeWidth)
var offset [32]byte
binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
key = utils.GetBinaryTreeKey(addr, offset[:])
@@ -308,7 +308,7 @@ func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.
values[groupOffset] = chunks[i : i+32]
if groupOffset == 255 || len(chunks)-i <= 32 {
- err = trie.UpdateStem(key[:31], values)
+ err = t.UpdateStem(key[:31], values)
if err != nil {
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
diff --git a/trie/binary_test.go b/trie/bintrie/trie_test.go
similarity index 97%
rename from trie/binary_test.go
rename to trie/bintrie/trie_test.go
index 082ca9b3030..e6cf795ee55 100644
--- a/trie/binary_test.go
+++ b/trie/bintrie/trie_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package trie
+package bintrie
import (
"bytes"
@@ -22,7 +22,6 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/trie/bintrie"
)
var (
@@ -152,7 +151,7 @@ func TestInsertDuplicateKey(t *testing.T) {
t.Fatal("invalid height")
}
// Verify that the value is updated
- if !bytes.Equal(tree.(*bintrie.StemNode).Values[1], twoKey[:]) {
+ if !bytes.Equal(tree.(*StemNode).Values[1], twoKey[:]) {
t.Fatal("invalid height")
}
}
diff --git a/trie/iterator.go b/trie/iterator.go
index e6fedf24309..80298ce48f1 100644
--- a/trie/iterator.go
+++ b/trie/iterator.go
@@ -405,7 +405,7 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
// loaded blob will be tracked, while it's not required here since
// all loaded nodes won't be linked to trie at all and track nodes
// may lead to out-of-memory issue.
- blob, err := it.trie.reader.node(path, common.BytesToHash(hash))
+ blob, err := it.trie.reader.Node(path, common.BytesToHash(hash))
if err != nil {
return nil, err
}
@@ -426,7 +426,7 @@ func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error)
// loaded blob will be tracked, while it's not required here since
// all loaded nodes won't be linked to trie at all and track nodes
// may lead to out-of-memory issue.
- return it.trie.reader.node(path, common.BytesToHash(hash))
+ return it.trie.reader.Node(path, common.BytesToHash(hash))
}
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
diff --git a/trie/proof.go b/trie/proof.go
index f3ed417094d..f9bc674d520 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -69,7 +69,7 @@ func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
// loaded blob will be tracked, while it's not required here since
// all loaded nodes won't be linked to trie at all and track nodes
// may lead to out-of-memory issue.
- blob, err := t.reader.node(prefix, common.BytesToHash(n))
+ blob, err := t.reader.Node(prefix, common.BytesToHash(n))
if err != nil {
log.Error("Unhandled trie error in Trie.Prove", "err", err)
return err
diff --git a/trie/trie.go b/trie/trie.go
index 98cf751f477..90b06b51dfc 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -55,7 +55,7 @@ type Trie struct {
uncommitted int
// reader is the handler trie can retrieve nodes from.
- reader *trieReader
+ reader *TrieReader
// Various tracers for capturing the modifications to trie
opTracer *opTracer
@@ -88,7 +88,7 @@ func (t *Trie) Copy() *Trie {
// empty, otherwise, the root node must be present in database or returns
// a MissingNodeError if not.
func New(id *ID, db database.NodeDatabase) (*Trie, error) {
- reader, err := newTrieReader(id.StateRoot, id.Owner, db)
+ reader, err := NewTrieReader(id.StateRoot, id.Owner, db)
if err != nil {
return nil, err
}
@@ -289,7 +289,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
if hash == nil {
return nil, origNode, 0, errors.New("non-consensus node")
}
- blob, err := t.reader.node(path, common.BytesToHash(hash))
+ blob, err := t.reader.Node(path, common.BytesToHash(hash))
return blob, origNode, 1, err
}
// Path still needs to be traversed, descend into children
@@ -655,7 +655,7 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
// node's original value. The rlp-encoded blob is preferred to be loaded from
// database because it's easy to decode node while complex to encode node to blob.
func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
- blob, err := t.reader.node(prefix, common.BytesToHash(n))
+ blob, err := t.reader.Node(prefix, common.BytesToHash(n))
if err != nil {
return nil, err
}
diff --git a/trie/trie_reader.go b/trie/trie_reader.go
index a42cdb0cf98..b776709d254 100644
--- a/trie/trie_reader.go
+++ b/trie/trie_reader.go
@@ -22,30 +22,30 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
-// trieReader is a wrapper of the underlying node reader. It's not safe
+// TrieReader is a wrapper of the underlying node reader. It's not safe
// for concurrent usage.
-type trieReader struct {
+type TrieReader struct {
owner common.Hash
reader database.NodeReader
banned map[string]struct{} // Marker to prevent node from being accessed, for tests
}
-// newTrieReader initializes the trie reader with the given node reader.
-func newTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*trieReader, error) {
+// NewTrieReader initializes the trie reader with the given node reader.
+func NewTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*TrieReader, error) {
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
- return &trieReader{owner: owner}, nil
+ return &TrieReader{owner: owner}, nil
}
reader, err := db.NodeReader(stateRoot)
if err != nil {
return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err}
}
- return &trieReader{owner: owner, reader: reader}, nil
+ return &TrieReader{owner: owner, reader: reader}, nil
}
// newEmptyReader initializes the pure in-memory reader. All read operations
// should be forbidden and returns the MissingNodeError.
-func newEmptyReader() *trieReader {
- return &trieReader{}
+func newEmptyReader() *TrieReader {
+ return &TrieReader{}
}
// node retrieves the rlp-encoded trie node with the provided trie node
@@ -54,7 +54,7 @@ func newEmptyReader() *trieReader {
//
// Don't modify the returned byte slice since it's not deep-copied and
// still be referenced by database.
-func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) {
+func (r *TrieReader) Node(path []byte, hash common.Hash) ([]byte, error) {
// Perform the logics in tests for preventing trie node access.
if r.banned != nil {
if _, ok := r.banned[string(path)]; ok {
diff --git a/trie/verkle.go b/trie/verkle.go
index e00ea21602c..8233aded380 100644
--- a/trie/verkle.go
+++ b/trie/verkle.go
@@ -41,13 +41,13 @@ var (
type VerkleTrie struct {
root verkle.VerkleNode
cache *utils.PointCache
- reader *trieReader
+ reader *TrieReader
tracer *prevalueTracer
}
// NewVerkleTrie constructs a verkle tree based on the specified root hash.
func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.PointCache) (*VerkleTrie, error) {
- reader, err := newTrieReader(root, common.Hash{}, db)
+ reader, err := NewTrieReader(root, common.Hash{}, db)
if err != nil {
return nil, err
}
@@ -72,6 +72,10 @@ func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.Poin
return t, nil
}
+func (t *VerkleTrie) FlatdbNodeResolver(path []byte) ([]byte, error) {
+ return t.reader.Node(path, common.Hash{})
+}
+
// GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value.
func (t *VerkleTrie) GetKey(key []byte) []byte {
@@ -443,7 +447,7 @@ func (t *VerkleTrie) ToDot() string {
}
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
- blob, err := t.reader.node(path, common.Hash{})
+ blob, err := t.reader.Node(path, common.Hash{})
if err != nil {
return nil, err
}
From c5db47cc4e04f856c6048c3d072940cbcb9b9b38 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Mon, 25 Aug 2025 15:52:56 +0200
Subject: [PATCH 03/10] Implement EIP-7864 binary trie with SHA256 hash
- Add binary trie implementation in trie/bintrie package
- Implement InternalNode, StemNode, Empty, and HashedNode types
- Add comprehensive test coverage for binary trie operations
- Switch from blake3 to sha256 hashing for compatibility
- Add iterator functionality for binary trie traversal
- Update PrevalueTracer interface to support binary operations
- Include proper error handling and edge case coverage
Co-authored-by: Guillaume Ballet
---
go.mod | 1 -
go.sum | 6 ---
trie/bintrie/binary_node.go | 31 +++++++++----
trie/bintrie/binary_node_test.go | 53 ++++++++--------------
trie/bintrie/hashed_node.go | 2 +-
trie/bintrie/hashed_node_test.go | 48 +-------------------
trie/bintrie/internal_node.go | 50 ++++++++++-----------
trie/bintrie/internal_node_test.go | 70 +++++++++++++++---------------
trie/bintrie/iterator.go | 47 +++++++++++++++-----
trie/bintrie/stem_node.go | 30 ++++++-------
trie/bintrie/stem_node_test.go | 8 ++--
trie/bintrie/trie.go | 3 +-
trie/bintrie/trie_test.go | 6 +--
trie/committer.go | 8 ++--
trie/tracer.go | 27 ++++++------
trie/trie.go | 6 +--
trie/verkle.go | 4 +-
17 files changed, 183 insertions(+), 217 deletions(-)
diff --git a/go.mod b/go.mod
index ffde28f005b..2da5b572098 100644
--- a/go.mod
+++ b/go.mod
@@ -62,7 +62,6 @@ require (
github.com/supranational/blst v0.3.14
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/urfave/cli/v2 v2.27.5
- github.com/zeebo/blake3 v0.2.4
go.uber.org/automaxprocs v1.5.2
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.36.0
diff --git a/go.sum b/go.sum
index 4a3aac1c129..7cb6fb677af 100644
--- a/go.sum
+++ b/go.sum
@@ -365,12 +365,6 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
-github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
-github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
-github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
-github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
-github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go
index b97adeb21e2..50d8c0b3ca1 100644
--- a/trie/bintrie/binary_node.go
+++ b/trie/bintrie/binary_node.go
@@ -35,6 +35,11 @@ const (
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
)
+const (
+ nodeTypeStem = iota + 1 // Stem node, contains a stem and a bitmap of values
+ nodeTypeInternal
+)
+
// BinaryNode is an interface for a binary trie node.
type BinaryNode interface {
Get([]byte, NodeResolverFn) ([]byte, error)
@@ -55,13 +60,13 @@ func SerializeNode(node BinaryNode) []byte {
switch n := (node).(type) {
case *InternalNode:
var serialized [65]byte
- serialized[0] = 1
- copy(serialized[1:33], n.Left.Hash().Bytes())
- copy(serialized[33:65], n.Right.Hash().Bytes())
+ serialized[0] = nodeTypeInternal
+ copy(serialized[1:33], n.left.Hash().Bytes())
+ copy(serialized[33:65], n.right.Hash().Bytes())
return serialized[:]
case *StemNode:
var serialized [32 + 256*32]byte
- serialized[0] = 2
+ serialized[0] = nodeTypeStem
copy(serialized[1:32], node.(*StemNode).Stem)
bitmap := serialized[32:64]
offset := 64
@@ -78,6 +83,8 @@ func SerializeNode(node BinaryNode) []byte {
}
}
+var invalidSerializedLength = errors.New("invalid serialized node length")
+
// DeserializeNode deserializes a binary trie node from a byte slice.
func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
if len(serialized) == 0 {
@@ -85,22 +92,28 @@ func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
}
switch serialized[0] {
- case 1:
+ case nodeTypeInternal:
if len(serialized) != 65 {
- return nil, errors.New("invalid serialized node length")
+ return nil, invalidSerializedLength
}
return &InternalNode{
depth: depth,
- Left: HashedNode(common.BytesToHash(serialized[1:33])),
- Right: HashedNode(common.BytesToHash(serialized[33:65])),
+ left: HashedNode(common.BytesToHash(serialized[1:33])),
+ right: HashedNode(common.BytesToHash(serialized[33:65])),
}, nil
- case 2:
+ case nodeTypeStem:
+ if len(serialized) < 64 {
+ return nil, invalidSerializedLength
+ }
var values [256][]byte
bitmap := serialized[32:64]
offset := 64
for i := range 256 {
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
+ if len(serialized) < offset+32 {
+ return nil, invalidSerializedLength
+ }
values[i] = serialized[offset : offset+32]
offset += 32
}
diff --git a/trie/bintrie/binary_node_test.go b/trie/bintrie/binary_node_test.go
index 5db6e111f08..b21daaab697 100644
--- a/trie/bintrie/binary_node_test.go
+++ b/trie/bintrie/binary_node_test.go
@@ -18,7 +18,6 @@ package bintrie
import (
"bytes"
- "errors"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -32,16 +31,16 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
node := &InternalNode{
depth: 5,
- Left: HashedNode(leftHash),
- Right: HashedNode(rightHash),
+ left: HashedNode(leftHash),
+ right: HashedNode(rightHash),
}
// Serialize the node
serialized := SerializeNode(node)
// Check the serialized format
- if serialized[0] != 1 {
- t.Errorf("Expected type byte to be 1, got %d", serialized[0])
+ if serialized[0] != nodeTypeInternal {
+ t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
}
if len(serialized) != 65 {
@@ -66,12 +65,12 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
}
// Check the left and right hashes
- if internalNode.Left.Hash() != leftHash {
- t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.Left.Hash())
+ if internalNode.left.Hash() != leftHash {
+ t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.left.Hash())
}
- if internalNode.Right.Hash() != rightHash {
- t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.Right.Hash())
+ if internalNode.right.Hash() != rightHash {
+ t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.right.Hash())
}
}
@@ -99,8 +98,8 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
serialized := SerializeNode(node)
// Check the serialized format
- if serialized[0] != 2 {
- t.Errorf("Expected type byte to be 2, got %d", serialized[0])
+ if serialized[0] != nodeTypeStem {
+ t.Errorf("Expected type byte to be %d, got %d", nodeTypeStem, serialized[0])
}
// Check the stem is correctly serialized
@@ -137,8 +136,13 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
}
// Check that other values are nil
- if stemNode.Values[1] != nil {
- t.Errorf("Expected nil value at index 1, got %x", stemNode.Values[1])
+ for i := range NodeWidth {
+ if i == 0 || i == 10 || i == 255 {
+ continue
+ }
+ if stemNode.Values[i] != nil {
+ t.Errorf("Expected nil value at index %d, got %x", i, stemNode.Values[i])
+ }
}
}
@@ -170,7 +174,7 @@ func TestDeserializeInvalidType(t *testing.T) {
// TestDeserializeInvalidLength tests deserialization with invalid data length
func TestDeserializeInvalidLength(t *testing.T) {
// InternalNode with type byte 1 but wrong length
- invalidData := []byte{1, 0, 0} // Too short for internal node
+ invalidData := []byte{nodeTypeInternal, 0, 0} // Too short for internal node
_, err := DeserializeNode(invalidData, 0)
if err == nil {
@@ -246,24 +250,3 @@ func TestKeyToPath(t *testing.T) {
})
}
}
-
-// Mock resolver function for testing
-func mockResolver(path []byte, hash common.Hash) ([]byte, error) {
- // Return a simple stem node for testing
- if hash == common.HexToHash("0x1234") {
- stem := make([]byte, 31)
- var values [256][]byte
- values[0] = common.HexToHash("0xabcd").Bytes()
- node := &StemNode{
- Stem: stem,
- Values: values[:],
- }
- return SerializeNode(node), nil
- }
- return nil, errors.New("node not found")
-}
-
-// Mock flush function for testing
-func mockFlushFn(path []byte, node BinaryNode) {
- // Just a stub for testing
-}
diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go
index e5ee79a460a..05e50db9fbf 100644
--- a/trie/bintrie/hashed_node.go
+++ b/trie/bintrie/hashed_node.go
@@ -58,7 +58,7 @@ func (h HashedNode) toDot(parent string, path string) string {
}
func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error {
- panic("not implemented") // TODO: Implement
+ return errors.New("collectNodes not implemented for hashed node")
}
func (h HashedNode) GetHeight() int {
diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go
index dbc6a7a2443..78f8ad72af7 100644
--- a/trie/bintrie/hashed_node_test.go
+++ b/trie/bintrie/hashed_node_test.go
@@ -104,52 +104,6 @@ func TestHashedNodeInsertValuesAtStem(t *testing.T) {
}
}
-// TestHashedNodeGet tests that Get panics (as per implementation)
-func TestHashedNodeGet(t *testing.T) {
- node := HashedNode(common.HexToHash("0x1234"))
-
- defer func() {
- if r := recover(); r == nil {
- t.Error("Expected panic for Get on HashedNode")
- }
- }()
-
- key := make([]byte, 32)
- _, _ = node.Get(key, nil)
-}
-
-// TestHashedNodeCollectNodes tests that CollectNodes panics (as per implementation)
-func TestHashedNodeCollectNodes(t *testing.T) {
- node := HashedNode(common.HexToHash("0x1234"))
-
- defer func() {
- if r := recover(); r == nil {
- t.Error("Expected panic for CollectNodes on HashedNode")
- }
- }()
-
- path := []byte{0, 1, 0}
- node.CollectNodes(path, func([]byte, BinaryNode) {})
-}
-
-// TestHashedNodeGetHeight tests that GetHeight panics (as per implementation)
-func TestHashedNodeGetHeight(t *testing.T) {
- node := HashedNode(common.HexToHash("0x1234"))
-
- defer func() {
- r := recover()
- if r == nil {
- t.Error("Expected panic for GetHeight on HashedNode")
- }
- // Check the panic message
- if r != "tried to get the height of a hashed node, this is a bug" {
- t.Errorf("Unexpected panic message: %v", r)
- }
- }()
-
- _ = node.GetHeight()
-}
-
// TestHashedNodeToDot tests the toDot method for visualization
func TestHashedNodeToDot(t *testing.T) {
hash := common.HexToHash("0x1234")
@@ -170,5 +124,5 @@ func TestHashedNodeToDot(t *testing.T) {
// Helper function
func contains(s, substr string) bool {
- return len(s) >= len(substr) && s[:len(s)] != "" && len(substr) > 0
+ return len(s) >= len(substr) && s != "" && len(substr) > 0
}
diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go
index 6287606c50e..af9fce5ef95 100644
--- a/trie/bintrie/internal_node.go
+++ b/trie/bintrie/internal_node.go
@@ -21,7 +21,7 @@ import (
"fmt"
"github.com/ethereum/go-ethereum/common"
- "github.com/zeebo/blake3"
+ "crypto/sha256"
)
func keyToPath(depth int, key []byte) ([]byte, error) {
@@ -41,7 +41,7 @@ func keyToPath(depth int, key []byte) ([]byte, error) {
// InternalNode is a binary trie internal node.
type InternalNode struct {
- Left, Right BinaryNode
+ left, right BinaryNode
depth int
}
@@ -54,9 +54,9 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
var child *BinaryNode
if bit == 0 {
- child = &bt.Left
+ child = &bt.left
} else {
- child = &bt.Right
+ child = &bt.right
}
if hn, ok := (*child).(HashedNode); ok {
@@ -96,22 +96,22 @@ func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn
// Copy creates a deep copy of the node.
func (bt *InternalNode) Copy() BinaryNode {
return &InternalNode{
- Left: bt.Left.Copy(),
- Right: bt.Right.Copy(),
+ left: bt.left.Copy(),
+ right: bt.right.Copy(),
depth: bt.depth,
}
}
// Hash returns the hash of the node.
func (bt *InternalNode) Hash() common.Hash {
- h := blake3.New()
- if bt.Left != nil {
- h.Write(bt.Left.Hash().Bytes())
+ h := sha256.New()
+ if bt.left != nil {
+ h.Write(bt.left.Hash().Bytes())
} else {
h.Write(zero[:])
}
- if bt.Right != nil {
- h.Write(bt.Right.Hash().Bytes())
+ if bt.right != nil {
+ h.Write(bt.right.Hash().Bytes())
} else {
h.Write(zero[:])
}
@@ -127,9 +127,9 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
err error
)
if bit == 0 {
- child = &bt.Left
+ child = &bt.left
} else {
- child = &bt.Right
+ child = &bt.right
}
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
@@ -139,21 +139,21 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
// CollectNodes collects all child nodes at a given path, and flushes it
// into the provided node collector.
func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error {
- if bt.Left != nil {
+ if bt.left != nil {
var p [256]byte
copy(p[:], path)
childpath := p[:len(path)]
childpath = append(childpath, 0)
- if err := bt.Left.CollectNodes(childpath, flushfn); err != nil {
+ if err := bt.left.CollectNodes(childpath, flushfn); err != nil {
return err
}
}
- if bt.Right != nil {
+ if bt.right != nil {
var p [256]byte
copy(p[:], path)
childpath := p[:len(path)]
childpath = append(childpath, 1)
- if err := bt.Right.CollectNodes(childpath, flushfn); err != nil {
+ if err := bt.right.CollectNodes(childpath, flushfn); err != nil {
return err
}
}
@@ -167,11 +167,11 @@ func (bt *InternalNode) GetHeight() int {
leftHeight int
rightHeight int
)
- if bt.Left != nil {
- leftHeight = bt.Left.GetHeight()
+ if bt.left != nil {
+ leftHeight = bt.left.GetHeight()
}
- if bt.Right != nil {
- rightHeight = bt.Right.GetHeight()
+ if bt.right != nil {
+ rightHeight = bt.right.GetHeight()
}
return 1 + max(leftHeight, rightHeight)
}
@@ -183,11 +183,11 @@ func (bt *InternalNode) toDot(parent, path string) string {
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
}
- if bt.Left != nil {
- ret = fmt.Sprintf("%s%s", ret, bt.Left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
+ if bt.left != nil {
+ ret = fmt.Sprintf("%s%s", ret, bt.left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
}
- if bt.Right != nil {
- ret = fmt.Sprintf("%s%s", ret, bt.Right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
+ if bt.right != nil {
+ ret = fmt.Sprintf("%s%s", ret, bt.right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
}
return ret
diff --git a/trie/bintrie/internal_node_test.go b/trie/bintrie/internal_node_test.go
index 142f1e95c1d..976e677177f 100644
--- a/trie/bintrie/internal_node_test.go
+++ b/trie/bintrie/internal_node_test.go
@@ -37,12 +37,12 @@ func TestInternalNodeGet(t *testing.T) {
node := &InternalNode{
depth: 0,
- Left: &StemNode{
+ left: &StemNode{
Stem: leftStem,
Values: leftValues[:],
depth: 1,
},
- Right: &StemNode{
+ right: &StemNode{
Stem: rightStem,
Values: rightValues[:],
depth: 1,
@@ -80,8 +80,8 @@ func TestInternalNodeGetWithResolver(t *testing.T) {
node := &InternalNode{
depth: 0,
- Left: hashedChild,
- Right: Empty{},
+ left: hashedChild,
+ right: Empty{},
}
// Mock resolver that returns a stem node
@@ -119,8 +119,8 @@ func TestInternalNodeInsert(t *testing.T) {
// Start with an internal node with empty children
node := &InternalNode{
depth: 0,
- Left: Empty{},
- Right: Empty{},
+ left: Empty{},
+ right: Empty{},
}
// Insert a value into the left subtree
@@ -139,9 +139,9 @@ func TestInternalNodeInsert(t *testing.T) {
}
// Check that left child is now a StemNode
- leftStem, ok := internalNode.Left.(*StemNode)
+ leftStem, ok := internalNode.left.(*StemNode)
if !ok {
- t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
+ t.Fatalf("Expected left child to be StemNode, got %T", internalNode.left)
}
// Check the inserted value
@@ -150,9 +150,9 @@ func TestInternalNodeInsert(t *testing.T) {
}
// Right child should still be Empty
- _, ok = internalNode.Right.(Empty)
+ _, ok = internalNode.right.(Empty)
if !ok {
- t.Errorf("Expected right child to remain Empty, got %T", internalNode.Right)
+ t.Errorf("Expected right child to remain Empty, got %T", internalNode.right)
}
}
@@ -176,8 +176,8 @@ func TestInternalNodeCopy(t *testing.T) {
node := &InternalNode{
depth: 0,
- Left: leftStem,
- Right: rightStem,
+ left: leftStem,
+ right: rightStem,
}
// Create a copy
@@ -193,14 +193,14 @@ func TestInternalNodeCopy(t *testing.T) {
}
// Check that children are copied
- copiedLeft, ok := copiedInternal.Left.(*StemNode)
+ copiedLeft, ok := copiedInternal.left.(*StemNode)
if !ok {
- t.Fatalf("Expected left child to be StemNode, got %T", copiedInternal.Left)
+ t.Fatalf("Expected left child to be StemNode, got %T", copiedInternal.left)
}
- copiedRight, ok := copiedInternal.Right.(*StemNode)
+ copiedRight, ok := copiedInternal.right.(*StemNode)
if !ok {
- t.Fatalf("Expected right child to be StemNode, got %T", copiedInternal.Right)
+ t.Fatalf("Expected right child to be StemNode, got %T", copiedInternal.right)
}
// Verify deep copy (children should be different objects)
@@ -225,8 +225,8 @@ func TestInternalNodeHash(t *testing.T) {
// Create an internal node
node := &InternalNode{
depth: 0,
- Left: HashedNode(common.HexToHash("0x1111")),
- Right: HashedNode(common.HexToHash("0x2222")),
+ left: HashedNode(common.HexToHash("0x1111")),
+ right: HashedNode(common.HexToHash("0x2222")),
}
hash1 := node.Hash()
@@ -238,7 +238,7 @@ func TestInternalNodeHash(t *testing.T) {
}
// Changing a child should change the hash
- node.Left = HashedNode(common.HexToHash("0x3333"))
+ node.left = HashedNode(common.HexToHash("0x3333"))
hash3 := node.Hash()
if hash1 == hash3 {
t.Error("Hash didn't change after modifying left child")
@@ -247,8 +247,8 @@ func TestInternalNodeHash(t *testing.T) {
// Test with nil children (should use zero hash)
nodeWithNil := &InternalNode{
depth: 0,
- Left: nil,
- Right: HashedNode(common.HexToHash("0x4444")),
+ left: nil,
+ right: HashedNode(common.HexToHash("0x4444")),
}
hashWithNil := nodeWithNil.Hash()
if hashWithNil == (common.Hash{}) {
@@ -271,12 +271,12 @@ func TestInternalNodeGetValuesAtStem(t *testing.T) {
node := &InternalNode{
depth: 0,
- Left: &StemNode{
+ left: &StemNode{
Stem: leftStem,
Values: leftValues[:],
depth: 1,
},
- Right: &StemNode{
+ right: &StemNode{
Stem: rightStem,
Values: rightValues[:],
depth: 1,
@@ -313,8 +313,8 @@ func TestInternalNodeInsertValuesAtStem(t *testing.T) {
// Start with an internal node with empty children
node := &InternalNode{
depth: 0,
- Left: Empty{},
- Right: Empty{},
+ left: Empty{},
+ right: Empty{},
}
// Insert values at a stem in the left subtree
@@ -334,9 +334,9 @@ func TestInternalNodeInsertValuesAtStem(t *testing.T) {
}
// Check that left child is now a StemNode with the values
- leftStem, ok := internalNode.Left.(*StemNode)
+ leftStem, ok := internalNode.left.(*StemNode)
if !ok {
- t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
+ t.Fatalf("Expected left child to be StemNode, got %T", internalNode.left)
}
if !bytes.Equal(leftStem.Values[5], values[5]) {
@@ -365,8 +365,8 @@ func TestInternalNodeCollectNodes(t *testing.T) {
node := &InternalNode{
depth: 0,
- Left: leftStem,
- Right: rightStem,
+ left: leftStem,
+ right: rightStem,
}
var collectedPaths [][]byte
@@ -410,12 +410,12 @@ func TestInternalNodeGetHeight(t *testing.T) {
// Right subtree: depth 1 (stem)
leftInternal := &InternalNode{
depth: 1,
- Left: &StemNode{
+ left: &StemNode{
Stem: make([]byte, 31),
Values: make([][]byte, 256),
depth: 2,
},
- Right: Empty{},
+ right: Empty{},
}
rightStem := &StemNode{
@@ -426,8 +426,8 @@ func TestInternalNodeGetHeight(t *testing.T) {
node := &InternalNode{
depth: 0,
- Left: leftInternal,
- Right: rightStem,
+ left: leftInternal,
+ right: rightStem,
}
height := node.GetHeight()
@@ -443,8 +443,8 @@ func TestInternalNodeDepthTooLarge(t *testing.T) {
// Create an internal node at max depth
node := &InternalNode{
depth: 31*8 + 1,
- Left: Empty{},
- Right: Empty{},
+ left: Empty{},
+ right: Empty{},
}
stem := make([]byte, 31)
diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go
index 94ce3895df3..ebe0162177b 100644
--- a/trie/bintrie/iterator.go
+++ b/trie/bintrie/iterator.go
@@ -69,9 +69,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
// recurse into both children
if context.Index == 0 {
- if _, isempty := node.Left.(Empty); node.Left != nil && !isempty {
- it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
- it.current = node.Left
+ if _, isempty := node.left.(Empty); node.left != nil && !isempty {
+ it.stack = append(it.stack, binaryNodeIteratorState{Node: node.left})
+ it.current = node.left
return it.Next(descend)
}
@@ -79,9 +79,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
}
if context.Index == 1 {
- if _, isempty := node.Right.(Empty); node.Right != nil && !isempty {
- it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
- it.current = node.Right
+ if _, isempty := node.right.(Empty); node.right != nil && !isempty {
+ it.stack = append(it.stack, binaryNodeIteratorState{Node: node.right})
+ it.current = node.right
return it.Next(descend)
}
@@ -127,9 +127,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
it.stack[len(it.stack)-1].Node = it.current
parent := &it.stack[len(it.stack)-2]
if parent.Index == 0 {
- parent.Node.(*InternalNode).Left = it.current
+ parent.Node.(*InternalNode).left = it.current
} else {
- parent.Node.(*InternalNode).Right = it.current
+ parent.Node.(*InternalNode).right = it.current
}
return it.Next(descend)
case Empty:
@@ -177,8 +177,9 @@ func (it *binaryNodeIterator) Path() []byte {
return path
}
+// NodeBlob returns the serialized bytes of the current node.
func (it *binaryNodeIterator) NodeBlob() []byte {
- panic("not completely implemented")
+ return SerializeNode(it.current)
}
// Leaf returns true iff the current node is a leaf node.
@@ -215,13 +216,35 @@ func (it *binaryNodeIterator) LeafBlob() []byte {
// iterator is not positioned at a leaf. Callers must not retain references
// to the value after calling Next.
func (it *binaryNodeIterator) LeafProof() [][]byte {
- _, ok := it.current.(*StemNode)
+ sn, ok := it.current.(*StemNode)
if !ok {
panic("LeafProof() called on an binary node iterator not at a leaf location")
}
- // return it.trie.Prove(leaf.Key())
- panic("not completely implemented")
+ proof := make([][]byte, 0, len(it.stack)+NodeWidth)
+
+ // Build proof by walking up the stack and collecting sibling hashes
+ for i := range it.stack[:len(it.stack)-2] {
+ state := it.stack[i]
+ internalNode := state.Node.(*InternalNode) // should panic if the node isn't an InternalNode
+
+ // Add the sibling hash to the proof
+ if state.Index == 0 {
+ // We came from left, so include right sibling
+ proof = append(proof, internalNode.right.Hash().Bytes())
+ } else {
+ // We came from right, so include left sibling
+ proof = append(proof, internalNode.left.Hash().Bytes())
+ }
+ }
+
+ // Add the stem and siblings
+ proof = append(proof, sn.Stem)
+ for _, v := range sn.Values {
+ proof = append(proof, v)
+ }
+
+ return proof
}
// AddResolver sets an intermediate database to use for looking up trie nodes
diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go
index 568d3ff4f26..91f2482fdfb 100644
--- a/trie/bintrie/stem_node.go
+++ b/trie/bintrie/stem_node.go
@@ -23,7 +23,7 @@ import (
"slices"
"github.com/ethereum/go-ethereum/common"
- "github.com/zeebo/blake3"
+ "crypto/sha256"
)
// StemNode represents a group of `NodeWith` values sharing the same stem.
@@ -47,13 +47,13 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNo
bt.depth++
var child, other *BinaryNode
if bitStem == 0 {
- new.Left = bt
- child = &new.Left
- other = &new.Right
+ new.left = bt
+ child = &new.left
+ other = &new.right
} else {
- new.Right = bt
- child = &new.Right
- other = &new.Left
+ new.right = bt
+ child = &new.right
+ other = &new.left
}
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
@@ -107,12 +107,12 @@ func (bt *StemNode) Hash() common.Hash {
var data [NodeWidth]common.Hash
for i, v := range bt.Values {
if v != nil {
- h := blake3.Sum256(v)
+ h := sha256.Sum256(v)
data[i] = common.BytesToHash(h[:])
}
}
- h := blake3.New()
+ h := sha256.New()
for level := 1; level <= 8; level++ {
for i := range NodeWidth / (1 << level) {
h.Reset()
@@ -157,13 +157,13 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv
bt.depth++
var child, other *BinaryNode
if bitStem == 0 {
- new.Left = bt
- child = &new.Left
- other = &new.Right
+ new.left = bt
+ child = &new.left
+ other = &new.right
} else {
- new.Right = bt
- child = &new.Right
- other = &new.Left
+ new.right = bt
+ child = &new.right
+ other = &new.left
}
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
diff --git a/trie/bintrie/stem_node_test.go b/trie/bintrie/stem_node_test.go
index 47dda38f592..4eedb434925 100644
--- a/trie/bintrie/stem_node_test.go
+++ b/trie/bintrie/stem_node_test.go
@@ -103,18 +103,18 @@ func TestStemNodeInsertDifferentStem(t *testing.T) {
}
// Original stem should be on the left (bit 0)
- leftStem, ok := internalNode.Left.(*StemNode)
+ leftStem, ok := internalNode.left.(*StemNode)
if !ok {
- t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
+ t.Fatalf("Expected left child to be StemNode, got %T", internalNode.left)
}
if !bytes.Equal(leftStem.Stem, stem1) {
t.Errorf("Left stem mismatch")
}
// New stem should be on the right (bit 1)
- rightStem, ok := internalNode.Right.(*StemNode)
+ rightStem, ok := internalNode.right.(*StemNode)
if !ok {
- t.Fatalf("Expected right child to be StemNode, got %T", internalNode.Right)
+ t.Fatalf("Expected right child to be StemNode, got %T", internalNode.right)
}
if !bytes.Equal(rightStem.Stem, key[:31]) {
t.Errorf("Right stem mismatch")
diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go
index 19543bf45cb..ca4ac511f85 100644
--- a/trie/bintrie/trie.go
+++ b/trie/bintrie/trie.go
@@ -44,6 +44,7 @@ func NewBinaryNode() BinaryNode {
type BinaryTrie struct {
root BinaryNode
reader *trie.TrieReader
+ tracer *trie.PrevalueTracer
}
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
@@ -246,7 +247,7 @@ func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
serialized := SerializeNode(node)
- nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
+ nodeset.AddNode(path, trienode.NewNodeWithPrev(common.Hash{}, serialized, t.tracer.Get(path)))
})
if err != nil {
panic(fmt.Errorf("CollectNodes failed: %v", err))
diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go
index e6cf795ee55..f0a9c8b8b01 100644
--- a/trie/bintrie/trie_test.go
+++ b/trie/bintrie/trie_test.go
@@ -42,7 +42,7 @@ func TestSingleEntry(t *testing.T) {
if tree.GetHeight() != 1 {
t.Fatal("invalid depth")
}
- expected := common.HexToHash("694545468677064fd833cddc8455762fe6b21c6cabe2fc172529e0f573181cd5")
+ expected := common.HexToHash("aab1060e04cb4f5dc6f697ae93156a95714debbf77d54238766adc5709282b6f")
got := tree.Hash()
if got != expected {
t.Fatalf("invalid tree root, got %x, want %x", got, expected)
@@ -63,7 +63,7 @@ func TestTwoEntriesDiffFirstBit(t *testing.T) {
if tree.GetHeight() != 2 {
t.Fatal("invalid height")
}
- if tree.Hash() != common.HexToHash("85fc622076752a6fcda2c886c18058d639066a83473d9684704b5a29455ed2ed") {
+ if tree.Hash() != common.HexToHash("dfc69c94013a8b3c65395625a719a87534a7cfd38719251ad8c8ea7fe79f065e") {
t.Fatal("invalid tree root")
}
}
@@ -190,7 +190,7 @@ func TestMerkleizeMultipleEntries(t *testing.T) {
}
}
got := tree.Hash()
- expected := common.HexToHash("8c74de28e6bb6b2296cae37cff16266e2dbf533bc204fa4cb0c237761ae8a2c8")
+ expected := common.HexToHash("9317155862f7a3867660ddd0966ff799a3d16aa4df1e70a7516eaa4a675191b5")
if got != expected {
t.Fatalf("invalid root, expected=%x, got = %x", expected, got)
}
diff --git a/trie/committer.go b/trie/committer.go
index a040868c6c8..2a2142e0ffa 100644
--- a/trie/committer.go
+++ b/trie/committer.go
@@ -29,12 +29,12 @@ import (
// insertion order.
type committer struct {
nodes *trienode.NodeSet
- tracer *prevalueTracer
+ tracer *PrevalueTracer
collectLeaf bool
}
// newCommitter creates a new committer or picks one from the pool.
-func newCommitter(nodeset *trienode.NodeSet, tracer *prevalueTracer, collectLeaf bool) *committer {
+func newCommitter(nodeset *trienode.NodeSet, tracer *PrevalueTracer, collectLeaf bool) *committer {
return &committer{
nodes: nodeset,
tracer: tracer,
@@ -142,7 +142,7 @@ func (c *committer) store(path []byte, n node) node {
// The node is embedded in its parent, in other words, this node
// will not be stored in the database independently, mark it as
// deleted only if the node was existent in database before.
- origin := c.tracer.get(path)
+ origin := c.tracer.Get(path)
if len(origin) != 0 {
c.nodes.AddNode(path, trienode.NewDeletedWithPrev(origin))
}
@@ -150,7 +150,7 @@ func (c *committer) store(path []byte, n node) node {
}
// Collect the dirty node to nodeset for return.
nhash := common.BytesToHash(hash)
- c.nodes.AddNode(path, trienode.NewNodeWithPrev(nhash, nodeToBytes(n), c.tracer.get(path)))
+ c.nodes.AddNode(path, trienode.NewNodeWithPrev(nhash, nodeToBytes(n), c.tracer.Get(path)))
// Collect the corresponding leaf node if it's required. We don't check
// full node since it's impossible to store value in fullNode. The key
diff --git a/trie/tracer.go b/trie/tracer.go
index b0542404a7c..184a5133254 100644
--- a/trie/tracer.go
+++ b/trie/tracer.go
@@ -94,20 +94,19 @@ func (t *opTracer) deletedList() [][]byte {
return paths
}
-// prevalueTracer tracks the original values of resolved trie nodes. Cached trie
+// PrevalueTracer tracks the original values of resolved trie nodes. Cached trie
// node values are expected to be immutable. A zero-size node value is treated as
// non-existent and should not occur in practice.
//
-// Note prevalueTracer is not thread-safe, callers should be responsible for
-// handling the concurrency issues by themselves.
-type prevalueTracer struct {
+// Note PrevalueTracer is thread-safe.
+type PrevalueTracer struct {
data map[string][]byte
lock sync.RWMutex
}
// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
-func newPrevalueTracer() *prevalueTracer {
- return &prevalueTracer{
+func newPrevalueTracer() *PrevalueTracer {
+ return &PrevalueTracer{
data: make(map[string][]byte),
}
}
@@ -115,16 +114,16 @@ func newPrevalueTracer() *prevalueTracer {
// put tracks the newly loaded trie node and caches its RLP-encoded
// blob internally. Do not modify the value outside this function,
// as it is not deep-copied.
-func (t *prevalueTracer) put(path []byte, val []byte) {
+func (t *PrevalueTracer) put(path []byte, val []byte) {
t.lock.Lock()
defer t.lock.Unlock()
t.data[string(path)] = val
}
-// get returns the cached trie node value. If the node is not found, nil will
+// Get returns the cached trie node value. If the node is not found, nil will
// be returned.
-func (t *prevalueTracer) get(path []byte) []byte {
+func (t *PrevalueTracer) Get(path []byte) []byte {
t.lock.RLock()
defer t.lock.RUnlock()
@@ -133,7 +132,7 @@ func (t *prevalueTracer) get(path []byte) []byte {
// hasList returns a list of flags indicating whether the corresponding trie nodes
// specified by the path exist in the trie.
-func (t *prevalueTracer) hasList(list [][]byte) []bool {
+func (t *PrevalueTracer) hasList(list [][]byte) []bool {
t.lock.RLock()
defer t.lock.RUnlock()
@@ -146,7 +145,7 @@ func (t *prevalueTracer) hasList(list [][]byte) []bool {
}
// values returns a list of values of the cached trie nodes.
-func (t *prevalueTracer) values() map[string][]byte {
+func (t *PrevalueTracer) values() map[string][]byte {
t.lock.RLock()
defer t.lock.RUnlock()
@@ -154,7 +153,7 @@ func (t *prevalueTracer) values() map[string][]byte {
}
// reset resets the cached content in the prevalueTracer.
-func (t *prevalueTracer) reset() {
+func (t *PrevalueTracer) reset() {
t.lock.Lock()
defer t.lock.Unlock()
@@ -162,12 +161,12 @@ func (t *prevalueTracer) reset() {
}
// copy returns a copied prevalueTracer instance.
-func (t *prevalueTracer) copy() *prevalueTracer {
+func (t *PrevalueTracer) copy() *PrevalueTracer {
t.lock.RLock()
defer t.lock.RUnlock()
// Shadow clone is used, as the cached trie node values are immutable
- return &prevalueTracer{
+ return &PrevalueTracer{
data: maps.Clone(t.data),
}
}
diff --git a/trie/trie.go b/trie/trie.go
index 90b06b51dfc..9ff13c50c02 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -59,7 +59,7 @@ type Trie struct {
// Various tracers for capturing the modifications to trie
opTracer *opTracer
- prevalueTracer *prevalueTracer
+ prevalueTracer *PrevalueTracer
}
// newFlag returns the cache flag value for a newly created node.
@@ -711,7 +711,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
}
nodes := trienode.NewNodeSet(t.owner)
for _, path := range paths {
- nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.get(path)))
+ nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.Get(path)))
}
return types.EmptyRootHash, nodes // case (b)
}
@@ -729,7 +729,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
}
nodes := trienode.NewNodeSet(t.owner)
for _, path := range t.deletedNodes() {
- nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.get(path)))
+ nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.Get(path)))
}
// If the number of changes is below 100, we let one thread handle it
t.root = newCommitter(nodes, t.prevalueTracer, collectLeaf).Commit(t.root, t.uncommitted > 100)
diff --git a/trie/verkle.go b/trie/verkle.go
index 8233aded380..4922e957da6 100644
--- a/trie/verkle.go
+++ b/trie/verkle.go
@@ -42,7 +42,7 @@ type VerkleTrie struct {
root verkle.VerkleNode
cache *utils.PointCache
reader *TrieReader
- tracer *prevalueTracer
+ tracer *PrevalueTracer
}
// NewVerkleTrie constructs a verkle tree based on the specified root hash.
@@ -293,7 +293,7 @@ func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
nodeset := trienode.NewNodeSet(common.Hash{})
for _, node := range nodes {
// Hash parameter is not used in pathdb
- nodeset.AddNode(node.Path, trienode.NewNodeWithPrev(common.Hash{}, node.SerializedBytes, t.tracer.get(node.Path)))
+ nodeset.AddNode(node.Path, trienode.NewNodeWithPrev(common.Hash{}, node.SerializedBytes, t.tracer.Get(node.Path)))
}
// Serialize root commitment form
return t.Hash(), nodeset
From 4ed90ca1994df4238f47fc06ff652559cda03d93 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Mon, 25 Aug 2025 15:35:38 -0400
Subject: [PATCH 04/10] revert changes in go.(mod|sum)
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 2da5b572098..363d7d3dfb0 100644
--- a/go.mod
+++ b/go.mod
@@ -115,7 +115,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
- github.com/klauspost/cpuid/v2 v2.0.12 // 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/mattn/go-runewidth v0.0.13 // indirect
diff --git a/go.sum b/go.sum
index 7cb6fb677af..099d432ba40 100644
--- a/go.sum
+++ b/go.sum
@@ -220,8 +220,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
-github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
+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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
From 93077a68388aeb6736d31b95415515c90b30c607 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Tue, 26 Aug 2025 04:29:41 -0400
Subject: [PATCH 05/10] gofmt broken file
---
trie/bintrie/internal_node.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go
index af9fce5ef95..5253772c571 100644
--- a/trie/bintrie/internal_node.go
+++ b/trie/bintrie/internal_node.go
@@ -20,8 +20,8 @@ import (
"errors"
"fmt"
- "github.com/ethereum/go-ethereum/common"
"crypto/sha256"
+ "github.com/ethereum/go-ethereum/common"
)
func keyToPath(depth int, key []byte) ([]byte, error) {
From f93d68e7787738c98db31be3d1ee6064c00cc9c8 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Tue, 26 Aug 2025 17:01:23 +0200
Subject: [PATCH 06/10] implement missing interface methods after rebase
---
trie/bintrie/trie.go | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go
index ca4ac511f85..05626abaa74 100644
--- a/trie/bintrie/trie.go
+++ b/trie/bintrie/trie.go
@@ -241,7 +241,7 @@ func (t *BinaryTrie) Hash() common.Hash {
// Commit writes all nodes to the trie's memory database, tracking the internal
// and external (for account tries) references.
-func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
+func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
root := t.root.(*InternalNode)
nodeset := trienode.NewNodeSet(common.Hash{})
@@ -254,7 +254,7 @@ func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
}
// Serialize root commitment form
- return t.Hash(), nodeset, nil
+ return t.Hash(), nodeset
}
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
@@ -318,3 +318,28 @@ func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Has
}
return nil
}
+
+func (t *BinaryTrie) PrefetchAccount(addresses []common.Address) error {
+ for _, addr := range addresses {
+ if _, err := t.GetAccount(addr); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// PrefetchStorage attempts to resolve specific storage slots from the database
+// to accelerate subsequent trie operations.
+func (t *BinaryTrie) PrefetchStorage(addr common.Address, keys [][]byte) error {
+ for _, key := range keys {
+ if _, err := t.GetStorage(addr, key); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Witness returns a set containing all trie nodes that have been accessed.
+func (t *BinaryTrie) Witness() map[string][]byte {
+ panic("not implemented")
+}
From 931b8fb13ed5ed3e0c6d227d64297930f5d16c2d Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Wed, 27 Aug 2025 14:50:47 +0200
Subject: [PATCH 07/10] fix linter
---
trie/bintrie/internal_node.go | 2 +-
trie/bintrie/stem_node.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go
index 5253772c571..a78daaf0217 100644
--- a/trie/bintrie/internal_node.go
+++ b/trie/bintrie/internal_node.go
@@ -17,10 +17,10 @@
package bintrie
import (
+ "crypto/sha256"
"errors"
"fmt"
- "crypto/sha256"
"github.com/ethereum/go-ethereum/common"
)
diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go
index 91f2482fdfb..07ef5d7e74b 100644
--- a/trie/bintrie/stem_node.go
+++ b/trie/bintrie/stem_node.go
@@ -18,12 +18,12 @@ package bintrie
import (
"bytes"
+ "crypto/sha256"
"errors"
"fmt"
"slices"
"github.com/ethereum/go-ethereum/common"
- "crypto/sha256"
)
// StemNode represents a group of `NodeWith` values sharing the same stem.
From 123723b9da31624a10668165a54cf15922aba1af Mon Sep 17 00:00:00 2001
From: rjl493456442
Date: Fri, 29 Aug 2025 17:59:54 +0800
Subject: [PATCH 08/10] Polish binary tree implementations (#551)
* trie: export PrevalueTracer
* trie, core/types: polish binary trie
* trie/bintrie: update tests
* trie/bintrie: fix incorrect node size
---
core/types/hashes.go | 3 +
trie/bintrie/binary_node.go | 5 +-
trie/bintrie/empty.go | 3 +-
trie/bintrie/empty_test.go | 2 +-
trie/bintrie/hashed_node.go | 2 +-
trie/bintrie/hashed_node_test.go | 2 +-
trie/bintrie/internal_node.go | 13 ++--
trie/bintrie/internal_node_test.go | 2 +-
trie/bintrie/iterator.go | 2 +-
trie/bintrie/key_encoding.go | 79 +++++++++++++++++++++
trie/bintrie/stem_node.go | 9 +--
trie/bintrie/stem_node_test.go | 6 +-
trie/bintrie/trie.go | 110 +++++++++++++++--------------
trie/bintrie/trie_test.go | 34 ++++-----
trie/proof.go | 2 +-
trie/tracer.go | 24 +++----
trie/trie.go | 16 ++---
trie/trie_reader.go | 20 +++---
trie/verkle.go | 10 +--
19 files changed, 213 insertions(+), 131 deletions(-)
create mode 100644 trie/bintrie/key_encoding.go
diff --git a/core/types/hashes.go b/core/types/hashes.go
index 05cfaeed748..22f1f946dc3 100644
--- a/core/types/hashes.go
+++ b/core/types/hashes.go
@@ -45,4 +45,7 @@ var (
// EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{}
+
+ // EmptyBinaryHash is the known hash of an empty binary trie.
+ EmptyBinaryHash = common.Hash{}
)
diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go
index 50d8c0b3ca1..1c003a6c8fd 100644
--- a/trie/bintrie/binary_node.go
+++ b/trie/bintrie/binary_node.go
@@ -43,8 +43,7 @@ const (
// BinaryNode is an interface for a binary trie node.
type BinaryNode interface {
Get([]byte, NodeResolverFn) ([]byte, error)
- Insert([]byte, []byte, NodeResolverFn) (BinaryNode, error)
- // Commit() common.Hash
+ Insert([]byte, []byte, NodeResolverFn, int) (BinaryNode, error)
Copy() BinaryNode
Hash() common.Hash
GetValuesAtStem([]byte, NodeResolverFn) ([][]byte, error)
@@ -65,7 +64,7 @@ func SerializeNode(node BinaryNode) []byte {
copy(serialized[33:65], n.right.Hash().Bytes())
return serialized[:]
case *StemNode:
- var serialized [32 + 256*32]byte
+ var serialized [32 + 32 + 256*32]byte
serialized[0] = nodeTypeStem
copy(serialized[1:32], node.(*StemNode).Stem)
bitmap := serialized[32:64]
diff --git a/trie/bintrie/empty.go b/trie/bintrie/empty.go
index 1effd7f03a0..7cfe373b35b 100644
--- a/trie/bintrie/empty.go
+++ b/trie/bintrie/empty.go
@@ -28,12 +28,13 @@ func (e Empty) Get(_ []byte, _ NodeResolverFn) ([]byte, error) {
return nil, nil
}
-func (e Empty) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNode, error) {
+func (e Empty) Insert(key []byte, value []byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
var values [256][]byte
values[key[31]] = value
return &StemNode{
Stem: slices.Clone(key[:31]),
Values: values[:],
+ depth: depth,
}, nil
}
diff --git a/trie/bintrie/empty_test.go b/trie/bintrie/empty_test.go
index a3c3681cad6..574ae1830be 100644
--- a/trie/bintrie/empty_test.go
+++ b/trie/bintrie/empty_test.go
@@ -47,7 +47,7 @@ func TestEmptyInsert(t *testing.T) {
key[31] = 0x34
value := common.HexToHash("0xabcd").Bytes()
- newNode, err := node.Insert(key, value, nil)
+ newNode, err := node.Insert(key, value, nil, 0)
if err != nil {
t.Fatalf("Failed to insert: %v", err)
}
diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go
index 05e50db9fbf..8f9fd66a59a 100644
--- a/trie/bintrie/hashed_node.go
+++ b/trie/bintrie/hashed_node.go
@@ -29,7 +29,7 @@ func (h HashedNode) Get(_ []byte, _ NodeResolverFn) ([]byte, error) {
panic("not implemented") // TODO: Implement
}
-func (h HashedNode) Insert(key []byte, value []byte, resolver NodeResolverFn) (BinaryNode, error) {
+func (h HashedNode) Insert(key []byte, value []byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
return nil, errors.New("insert not implemented for hashed node")
}
diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go
index 78f8ad72af7..0c19ae0c57d 100644
--- a/trie/bintrie/hashed_node_test.go
+++ b/trie/bintrie/hashed_node_test.go
@@ -62,7 +62,7 @@ func TestHashedNodeInsert(t *testing.T) {
key := make([]byte, 32)
value := make([]byte, 32)
- _, err := node.Insert(key, value, nil)
+ _, err := node.Insert(key, value, nil, 0)
if err == nil {
t.Fatal("Expected error for Insert on HashedNode")
}
diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go
index a78daaf0217..70dc54a93b0 100644
--- a/trie/bintrie/internal_node.go
+++ b/trie/bintrie/internal_node.go
@@ -25,17 +25,14 @@ import (
)
func keyToPath(depth int, key []byte) ([]byte, error) {
- path := make([]byte, 0, depth+1)
-
if depth > 31*8 {
return nil, errors.New("node too deep")
}
-
+ path := make([]byte, 0, depth+1)
for i := range depth + 1 {
bit := key[i/8] >> (7 - (i % 8)) & 1
path = append(path, bit)
}
-
return path, nil
}
@@ -81,16 +78,16 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([
func (bt *InternalNode) Get(key []byte, resolver NodeResolverFn) ([]byte, error) {
values, err := bt.GetValuesAtStem(key[:31], resolver)
if err != nil {
- return nil, fmt.Errorf("Get error: %w", err)
+ return nil, fmt.Errorf("get error: %w", err)
}
return values[key[31]], nil
}
// Insert inserts a new key-value pair into the trie.
-func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn) (BinaryNode, error) {
+func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
var values [256][]byte
values[key[31]] = value
- return bt.InsertValuesAtStem(key[:31], values[:], resolver, 0)
+ return bt.InsertValuesAtStem(key[:31], values[:], resolver, depth)
}
// Copy creates a deep copy of the node.
@@ -121,11 +118,11 @@ func (bt *InternalNode) Hash() common.Hash {
// InsertValuesAtStem inserts a full value group at the given stem in the internal node.
// Already-existing values will be overwritten.
func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) {
- bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
var (
child *BinaryNode
err error
)
+ bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
if bit == 0 {
child = &bt.left
} else {
diff --git a/trie/bintrie/internal_node_test.go b/trie/bintrie/internal_node_test.go
index 976e677177f..158d8b7147d 100644
--- a/trie/bintrie/internal_node_test.go
+++ b/trie/bintrie/internal_node_test.go
@@ -128,7 +128,7 @@ func TestInternalNodeInsert(t *testing.T) {
leftKey[31] = 10
leftValue := common.HexToHash("0x0101").Bytes()
- newNode, err := node.Insert(leftKey, leftValue, nil)
+ newNode, err := node.Insert(leftKey, leftValue, nil, 0)
if err != nil {
t.Fatalf("Failed to insert: %v", err)
}
diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go
index ebe0162177b..66564778125 100644
--- a/trie/bintrie/iterator.go
+++ b/trie/bintrie/iterator.go
@@ -114,7 +114,7 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
return it.Next(descend)
case HashedNode:
// resolve the node
- data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
+ data, err := it.trie.nodeResolver(it.Path(), common.Hash(node))
if err != nil {
panic(err)
}
diff --git a/trie/bintrie/key_encoding.go b/trie/bintrie/key_encoding.go
new file mode 100644
index 00000000000..13c20573710
--- /dev/null
+++ b/trie/bintrie/key_encoding.go
@@ -0,0 +1,79 @@
+// Copyright 2025 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 bintrie
+
+import (
+ "bytes"
+ "crypto/sha256"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/holiman/uint256"
+)
+
+const (
+ BasicDataLeafKey = 0
+ CodeHashLeafKey = 1
+ BasicDataCodeSizeOffset = 5
+ BasicDataNonceOffset = 8
+ BasicDataBalanceOffset = 16
+)
+
+var (
+ zeroHash = common.Hash{}
+ codeOffset = uint256.NewInt(128)
+)
+
+func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
+ hasher := sha256.New()
+ hasher.Write(zeroHash[:12])
+ hasher.Write(addr[:])
+ hasher.Write(key[:31])
+ k := hasher.Sum(nil)
+ k[31] = key[31]
+ return k
+}
+
+func GetBinaryTreeKeyCodeHash(addr common.Address) []byte {
+ var k [32]byte
+ k[31] = CodeHashLeafKey
+ return GetBinaryTreeKey(addr, k[:])
+}
+
+func GetBinaryTreeKeyStorageSlot(address common.Address, key []byte) []byte {
+ var k [32]byte
+
+ // Case when the key belongs to the account header
+ if bytes.Equal(key[:31], zeroHash[:31]) && key[31] < 64 {
+ k[31] = 64 + key[31]
+ return GetBinaryTreeKey(address, k[:])
+ }
+
+ // Set the main storage offset
+ // note that the first 64 bytes of the main offset storage
+ // are unreachable, which is consistent with the spec and
+ // what verkle does.
+ k[0] = 1 // 1 << 248
+ copy(k[1:], key[:31])
+ k[31] = key[31]
+
+ return GetBinaryTreeKey(address, k[:])
+}
+
+func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []byte {
+ chunkOffset := new(uint256.Int).Add(codeOffset, chunknr).Bytes()
+ return GetBinaryTreeKey(address, chunkOffset)
+}
diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go
index 07ef5d7e74b..7422175bd0e 100644
--- a/trie/bintrie/stem_node.go
+++ b/trie/bintrie/stem_node.go
@@ -39,7 +39,7 @@ func (bt *StemNode) Get(key []byte, _ NodeResolverFn) ([]byte, error) {
}
// Insert inserts a new key-value pair into the node.
-func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNode, error) {
+func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int) (BinaryNode, error) {
if !bytes.Equal(bt.Stem, key[:31]) {
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
@@ -59,7 +59,7 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNo
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
if bitKey == bitStem {
var err error
- *child, err = (*child).Insert(key, value, nil)
+ *child, err = (*child).Insert(key, value, nil, depth+1)
if err != nil {
return new, fmt.Errorf("insert error: %w", err)
}
@@ -70,16 +70,14 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNo
*other = &StemNode{
Stem: slices.Clone(key[:31]),
Values: values[:],
- depth: new.depth + 1,
+ depth: depth + 1,
}
}
-
return new, nil
}
if len(value) != 32 {
return bt, errors.New("invalid insertion: value length")
}
-
bt.Values[key[31]] = value
return bt, nil
}
@@ -181,7 +179,6 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv
depth: new.depth + 1,
}
}
-
return new, nil
}
diff --git a/trie/bintrie/stem_node_test.go b/trie/bintrie/stem_node_test.go
index 4eedb434925..e0ffd5c3c84 100644
--- a/trie/bintrie/stem_node_test.go
+++ b/trie/bintrie/stem_node_test.go
@@ -45,7 +45,7 @@ func TestStemNodeInsertSameStem(t *testing.T) {
key[31] = 10
value := common.HexToHash("0x0202").Bytes()
- newNode, err := node.Insert(key, value, nil)
+ newNode, err := node.Insert(key, value, nil, 0)
if err != nil {
t.Fatalf("Failed to insert: %v", err)
}
@@ -86,7 +86,7 @@ func TestStemNodeInsertDifferentStem(t *testing.T) {
key[0] = 0x80 // First bit is 1 instead of 0
value := common.HexToHash("0x0202").Bytes()
- newNode, err := node.Insert(key, value, nil)
+ newNode, err := node.Insert(key, value, nil, 0)
if err != nil {
t.Fatalf("Failed to insert: %v", err)
}
@@ -137,7 +137,7 @@ func TestStemNodeInsertInvalidValueLength(t *testing.T) {
copy(key[:31], stem)
invalidValue := []byte{1, 2, 3} // Not 32 bytes
- _, err := node.Insert(key, invalidValue, nil)
+ _, err := node.Insert(key, invalidValue, nil, 0)
if err == nil {
t.Fatal("Expected error for invalid value length")
}
diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go
index 05626abaa74..d0049f4a758 100644
--- a/trie/bintrie/trie.go
+++ b/trie/bintrie/trie.go
@@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb/database"
"github.com/holiman/uint256"
)
@@ -39,11 +38,10 @@ func NewBinaryNode() BinaryNode {
return Empty{}
}
-// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
-// interface so that Verkle trees can be reused verbatim.
+// BinaryTrie is the implementation of https://eips.ethereum.org/EIPS/eip-7864.
type BinaryTrie struct {
root BinaryNode
- reader *trie.TrieReader
+ reader *trie.Reader
tracer *trie.PrevalueTracer
}
@@ -55,36 +53,43 @@ func (t *BinaryTrie) ToDot() string {
// NewBinaryTrie creates a new binary trie.
func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) {
- reader, err := trie.NewTrieReader(root, common.Hash{}, db)
+ reader, err := trie.NewReader(root, common.Hash{}, db)
if err != nil {
return nil, err
}
- // Parse the root verkle node if it's not empty.
- node := NewBinaryNode()
- if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
- blob, err := reader.Node(nil, common.Hash{})
+ t := &BinaryTrie{
+ root: NewBinaryNode(),
+ reader: reader,
+ tracer: trie.NewPrevalueTracer(),
+ }
+ // Parse the root node if it's not empty
+ if root != types.EmptyBinaryHash && root != types.EmptyRootHash {
+ blob, err := t.nodeResolver(nil, root)
if err != nil {
return nil, err
}
- node, err = DeserializeNode(blob, 0)
+ node, err := DeserializeNode(blob, 0)
if err != nil {
return nil, err
}
+ t.root = node
}
- return &BinaryTrie{
- root: node,
- reader: reader,
- }, nil
+ return t, nil
}
-// FlatdbNodeResolver is a node resolver that reads nodes from the flatdb.
-func (t *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
+// nodeResolver is a node resolver that reads nodes from the flatdb.
+func (t *BinaryTrie) nodeResolver(path []byte, hash common.Hash) ([]byte, error) {
// empty nodes will be serialized as common.Hash{}, so capture
// this special use case.
if hash == (common.Hash{}) {
return nil, nil // empty node
}
- return t.reader.Node(path, hash)
+ blob, err := t.reader.Node(path, hash)
+ if err != nil {
+ return nil, err
+ }
+ t.tracer.Put(path, blob)
+ return blob, nil
}
// GetKey returns the sha3 preimage of a hashed key that was previously used
@@ -93,30 +98,23 @@ func (t *BinaryTrie) GetKey(key []byte) []byte {
return key
}
-// Get returns the value for key stored in the trie. The value bytes must
-// not be modified by the caller. If a node was not found in the database, a
-// trie.MissingNodeError is returned.
-func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
- return t.root.Get(utils.GetBinaryTreeKey(addr, key), t.FlatdbNodeResolver)
-}
-
// GetWithHashedKey returns the value, assuming that the key has already
// been hashed.
func (t *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
- return t.root.Get(key, t.FlatdbNodeResolver)
+ return t.root.Get(key, t.nodeResolver)
}
// GetAccount returns the account information for the given address.
func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
- acc := &types.StateAccount{}
- versionkey := utils.GetBinaryTreeKey(addr, zero[:])
var (
values [][]byte
err error
+ acc = &types.StateAccount{}
+ key = GetBinaryTreeKey(addr, zero[:])
)
switch r := t.root.(type) {
case *InternalNode:
- values, err = r.GetValuesAtStem(versionkey[:31], t.FlatdbNodeResolver)
+ values, err = r.GetValuesAtStem(key[:31], t.nodeResolver)
case *StemNode:
values = r.Values
case Empty:
@@ -130,47 +128,53 @@ func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error
return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
}
- // The following code is required for the MPT->VKT conversion.
- // An account can be partially migrated, where storage slots were moved to the VKT
+ // The following code is required for the MPT->Binary conversion.
+ // An account can be partially migrated, where storage slots were moved to the binary
// but not yet the account. This means some account information as (header) storage slots
- // are in the VKT but basic account information must be read in the base tree (MPT).
+ // are in the binary trie but basic account information must be read in the base tree (MPT).
// TODO: we can simplify this logic depending if the conversion is in progress or finished.
emptyAccount := true
-
- for i := 0; values != nil && i <= utils.CodeHashLeafKey && emptyAccount; i++ {
+ for i := 0; values != nil && i <= CodeHashLeafKey && emptyAccount; i++ {
emptyAccount = emptyAccount && values[i] == nil
}
if emptyAccount {
return nil, nil
}
- // if the account has been deleted, then values[10] will be 0 and not nil. If it has
+ // If the account has been deleted, then values[10] will be 0 and not nil. If it has
// been recreated after that, then its code keccak will NOT be 0. So return `nil` if
// the nonce, and values[10], and code keccak is 0.
- if bytes.Equal(values[utils.BasicDataLeafKey], zero[:]) && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeHashLeafKey], zero[:]) {
+ if bytes.Equal(values[BasicDataLeafKey], zero[:]) && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[CodeHashLeafKey], zero[:]) {
return nil, nil
}
- acc.Nonce = binary.BigEndian.Uint64(values[utils.BasicDataLeafKey][utils.BasicDataNonceOffset:])
+ acc.Nonce = binary.BigEndian.Uint64(values[BasicDataLeafKey][BasicDataNonceOffset:])
var balance [16]byte
- copy(balance[:], values[utils.BasicDataLeafKey][utils.BasicDataBalanceOffset:])
+ copy(balance[:], values[BasicDataLeafKey][BasicDataBalanceOffset:])
acc.Balance = new(uint256.Int).SetBytes(balance[:])
- acc.CodeHash = values[utils.CodeHashLeafKey]
+ acc.CodeHash = values[CodeHashLeafKey]
return acc, nil
}
+// GetStorage returns the value for key stored in the trie. The value bytes must
+// not be modified by the caller. If a node was not found in the database, a
+// trie.MissingNodeError is returned.
+func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
+ return t.root.Get(GetBinaryTreeKey(addr, key), t.nodeResolver)
+}
+
// UpdateAccount updates the account information for the given address.
func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
var (
err error
basicData [32]byte
values = make([][]byte, NodeWidth)
- stem = utils.GetBinaryTreeKey(addr, zero[:])
+ stem = GetBinaryTreeKey(addr, zero[:])
)
+ binary.BigEndian.PutUint32(basicData[BasicDataCodeSizeOffset-1:], uint32(codeLen))
+ binary.BigEndian.PutUint64(basicData[BasicDataNonceOffset:], acc.Nonce)
- binary.BigEndian.PutUint32(basicData[utils.BasicDataCodeSizeOffset-1:], uint32(codeLen))
- binary.BigEndian.PutUint64(basicData[utils.BasicDataNonceOffset:], acc.Nonce)
// Because the balance is a max of 16 bytes, truncate
// the extra values. This happens in devmode, where
// 0xff**32 is allocated to the developer account.
@@ -181,33 +185,33 @@ func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount,
balanceBytes = balanceBytes[16:]
}
copy(basicData[32-len(balanceBytes):], balanceBytes[:])
- values[utils.BasicDataLeafKey] = basicData[:]
- values[utils.CodeHashLeafKey] = acc.CodeHash[:]
+ values[BasicDataLeafKey] = basicData[:]
+ values[CodeHashLeafKey] = acc.CodeHash[:]
- t.root, err = t.root.InsertValuesAtStem(stem, values, t.FlatdbNodeResolver, 0)
+ t.root, err = t.root.InsertValuesAtStem(stem, values, t.nodeResolver, 0)
return err
}
// UpdateStem updates the values for the given stem key.
func (t *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
var err error
- t.root, err = t.root.InsertValuesAtStem(key, values, t.FlatdbNodeResolver, 0)
+ t.root, err = t.root.InsertValuesAtStem(key, values, t.nodeResolver, 0)
return err
}
-// Update associates key with value in the trie. If value has length zero, any
+// UpdateStorage associates key with value in the trie. If value has length zero, any
// existing value is deleted from the trie. The value bytes must not be modified
// by the caller while they are stored in the trie. If a node was not found in the
// database, a trie.MissingNodeError is returned.
func (t *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
- k := utils.GetBinaryTreeKeyStorageSlot(address, key)
+ k := GetBinaryTreeKeyStorageSlot(address, key)
var v [32]byte
if len(value) >= 32 {
copy(v[:], value[:32])
} else {
copy(v[32-len(value):], value[:])
}
- root, err := t.root.Insert(k, v[:], t.FlatdbNodeResolver)
+ root, err := t.root.Insert(k, v[:], t.nodeResolver, 0)
if err != nil {
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
}
@@ -220,12 +224,12 @@ func (t *BinaryTrie) DeleteAccount(addr common.Address) error {
return nil
}
-// Delete removes any existing value for key from the trie. If a node was not
+// DeleteStorage removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
- k := utils.GetBinaryTreeKey(addr, key)
+ k := GetBinaryTreeKey(addr, key)
var zero [32]byte
- root, err := t.root.Insert(k, zero[:], t.FlatdbNodeResolver)
+ root, err := t.root.Insert(k, zero[:], t.nodeResolver, 0)
if err != nil {
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
}
@@ -290,6 +294,8 @@ func (t *BinaryTrie) IsVerkle() bool {
return true
}
+// UpdateContractCode updates the contract code into the trie.
+//
// Note: the basic data leaf needs to have been previously created for this to work
func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
var (
@@ -304,7 +310,7 @@ func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Has
values = make([][]byte, NodeWidth)
var offset [32]byte
binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
- key = utils.GetBinaryTreeKey(addr, offset[:])
+ key = GetBinaryTreeKey(addr, offset[:])
}
values[groupOffset] = chunks[i : i+32]
diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go
index f0a9c8b8b01..84f76895494 100644
--- a/trie/bintrie/trie_test.go
+++ b/trie/bintrie/trie_test.go
@@ -35,7 +35,7 @@ var (
func TestSingleEntry(t *testing.T) {
tree := NewBinaryNode()
- tree, err := tree.Insert(zeroKey[:], oneKey[:], nil)
+ tree, err := tree.Insert(zeroKey[:], oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -52,11 +52,11 @@ func TestSingleEntry(t *testing.T) {
func TestTwoEntriesDiffFirstBit(t *testing.T) {
var err error
tree := NewBinaryNode()
- tree, err = tree.Insert(zeroKey[:], oneKey[:], nil)
+ tree, err = tree.Insert(zeroKey[:], oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000000").Bytes(), twoKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000000").Bytes(), twoKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -71,19 +71,19 @@ func TestTwoEntriesDiffFirstBit(t *testing.T) {
func TestOneStemColocatedValues(t *testing.T) {
var err error
tree := NewBinaryNode()
- tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000009").Bytes(), threeKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000009").Bytes(), threeKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(common.HexToHash("00000000000000000000000000000000000000000000000000000000000000FF").Bytes(), fourKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("00000000000000000000000000000000000000000000000000000000000000FF").Bytes(), fourKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -96,20 +96,20 @@ func TestTwoStemColocatedValues(t *testing.T) {
var err error
tree := NewBinaryNode()
// stem: 0...0
- tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("0000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
// stem: 10...0
- tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000003").Bytes(), oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil)
+ tree, err = tree.Insert(common.HexToHash("8000000000000000000000000000000000000000000000000000000000000004").Bytes(), twoKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -124,11 +124,11 @@ func TestTwoKeysMatchFirst42Bits(t *testing.T) {
// key1 and key 2 have the same prefix of 42 bits (b0*42+b1+b1) and differ after.
key1 := common.HexToHash("0000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0").Bytes()
key2 := common.HexToHash("0000000000E00000000000000000000000000000000000000000000000000000").Bytes()
- tree, err = tree.Insert(key1, oneKey[:], nil)
+ tree, err = tree.Insert(key1, oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(key2, twoKey[:], nil)
+ tree, err = tree.Insert(key2, twoKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -139,11 +139,11 @@ func TestTwoKeysMatchFirst42Bits(t *testing.T) {
func TestInsertDuplicateKey(t *testing.T) {
var err error
tree := NewBinaryNode()
- tree, err = tree.Insert(oneKey[:], oneKey[:], nil)
+ tree, err = tree.Insert(oneKey[:], oneKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
- tree, err = tree.Insert(oneKey[:], twoKey[:], nil)
+ tree, err = tree.Insert(oneKey[:], twoKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -161,7 +161,7 @@ func TestLargeNumberOfEntries(t *testing.T) {
for i := range 256 {
var key [32]byte
key[0] = byte(i)
- tree, err = tree.Insert(key[:], ffKey[:], nil)
+ tree, err = tree.Insert(key[:], ffKey[:], nil, 0)
if err != nil {
t.Fatal(err)
}
@@ -184,7 +184,7 @@ func TestMerkleizeMultipleEntries(t *testing.T) {
for i, key := range keys {
var v [32]byte
binary.LittleEndian.PutUint64(v[:8], uint64(i))
- tree, err = tree.Insert(key, v[:], nil)
+ tree, err = tree.Insert(key, v[:], nil, 0)
if err != nil {
t.Fatal(err)
}
diff --git a/trie/proof.go b/trie/proof.go
index f9bc674d520..1a06ed5d5e3 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -571,7 +571,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu
root: root,
reader: newEmptyReader(),
opTracer: newOpTracer(),
- prevalueTracer: newPrevalueTracer(),
+ prevalueTracer: NewPrevalueTracer(),
}
if empty {
tr.root = nil
diff --git a/trie/tracer.go b/trie/tracer.go
index 184a5133254..04122d1384f 100644
--- a/trie/tracer.go
+++ b/trie/tracer.go
@@ -104,17 +104,17 @@ type PrevalueTracer struct {
lock sync.RWMutex
}
-// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
-func newPrevalueTracer() *PrevalueTracer {
+// NewPrevalueTracer initializes the tracer for capturing resolved trie nodes.
+func NewPrevalueTracer() *PrevalueTracer {
return &PrevalueTracer{
data: make(map[string][]byte),
}
}
-// put tracks the newly loaded trie node and caches its RLP-encoded
+// Put tracks the newly loaded trie node and caches its RLP-encoded
// blob internally. Do not modify the value outside this function,
// as it is not deep-copied.
-func (t *PrevalueTracer) put(path []byte, val []byte) {
+func (t *PrevalueTracer) Put(path []byte, val []byte) {
t.lock.Lock()
defer t.lock.Unlock()
@@ -130,9 +130,9 @@ func (t *PrevalueTracer) Get(path []byte) []byte {
return t.data[string(path)]
}
-// hasList returns a list of flags indicating whether the corresponding trie nodes
+// HasList returns a list of flags indicating whether the corresponding trie nodes
// specified by the path exist in the trie.
-func (t *PrevalueTracer) hasList(list [][]byte) []bool {
+func (t *PrevalueTracer) HasList(list [][]byte) []bool {
t.lock.RLock()
defer t.lock.RUnlock()
@@ -144,24 +144,24 @@ func (t *PrevalueTracer) hasList(list [][]byte) []bool {
return exists
}
-// values returns a list of values of the cached trie nodes.
-func (t *PrevalueTracer) values() map[string][]byte {
+// Values returns a list of values of the cached trie nodes.
+func (t *PrevalueTracer) Values() map[string][]byte {
t.lock.RLock()
defer t.lock.RUnlock()
return maps.Clone(t.data)
}
-// reset resets the cached content in the prevalueTracer.
-func (t *PrevalueTracer) reset() {
+// Reset resets the cached content in the prevalueTracer.
+func (t *PrevalueTracer) Reset() {
t.lock.Lock()
defer t.lock.Unlock()
clear(t.data)
}
-// copy returns a copied prevalueTracer instance.
-func (t *PrevalueTracer) copy() *PrevalueTracer {
+// Copy returns a copied prevalueTracer instance.
+func (t *PrevalueTracer) Copy() *PrevalueTracer {
t.lock.RLock()
defer t.lock.RUnlock()
diff --git a/trie/trie.go b/trie/trie.go
index 9ff13c50c02..630462f8ca5 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -55,7 +55,7 @@ type Trie struct {
uncommitted int
// reader is the handler trie can retrieve nodes from.
- reader *TrieReader
+ reader *Reader
// Various tracers for capturing the modifications to trie
opTracer *opTracer
@@ -77,7 +77,7 @@ func (t *Trie) Copy() *Trie {
uncommitted: t.uncommitted,
reader: t.reader,
opTracer: t.opTracer.copy(),
- prevalueTracer: t.prevalueTracer.copy(),
+ prevalueTracer: t.prevalueTracer.Copy(),
}
}
@@ -88,7 +88,7 @@ func (t *Trie) Copy() *Trie {
// empty, otherwise, the root node must be present in database or returns
// a MissingNodeError if not.
func New(id *ID, db database.NodeDatabase) (*Trie, error) {
- reader, err := NewTrieReader(id.StateRoot, id.Owner, db)
+ reader, err := NewReader(id.StateRoot, id.Owner, db)
if err != nil {
return nil, err
}
@@ -96,7 +96,7 @@ func New(id *ID, db database.NodeDatabase) (*Trie, error) {
owner: id.Owner,
reader: reader,
opTracer: newOpTracer(),
- prevalueTracer: newPrevalueTracer(),
+ prevalueTracer: NewPrevalueTracer(),
}
if id.Root != (common.Hash{}) && id.Root != types.EmptyRootHash {
rootnode, err := trie.resolveAndTrack(id.Root[:], nil)
@@ -659,7 +659,7 @@ func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
if err != nil {
return nil, err
}
- t.prevalueTracer.put(prefix, blob)
+ t.prevalueTracer.Put(prefix, blob)
// The returned node blob won't be changed afterward. No need to
// deep-copy the slice.
@@ -673,7 +673,7 @@ func (t *Trie) deletedNodes() [][]byte {
var (
pos int
list = t.opTracer.deletedList()
- flags = t.prevalueTracer.hasList(list)
+ flags = t.prevalueTracer.HasList(list)
)
for i := 0; i < len(list); i++ {
if flags[i] {
@@ -753,7 +753,7 @@ func (t *Trie) hashRoot() []byte {
// Witness returns a set containing all trie nodes that have been accessed.
func (t *Trie) Witness() map[string][]byte {
- return t.prevalueTracer.values()
+ return t.prevalueTracer.Values()
}
// Reset drops the referenced root node and cleans all internal state.
@@ -763,6 +763,6 @@ func (t *Trie) Reset() {
t.unhashed = 0
t.uncommitted = 0
t.opTracer.reset()
- t.prevalueTracer.reset()
+ t.prevalueTracer.Reset()
t.committed = false
}
diff --git a/trie/trie_reader.go b/trie/trie_reader.go
index b776709d254..42fe4d72c7b 100644
--- a/trie/trie_reader.go
+++ b/trie/trie_reader.go
@@ -22,39 +22,39 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
-// TrieReader is a wrapper of the underlying node reader. It's not safe
+// Reader is a wrapper of the underlying database reader. It's not safe
// for concurrent usage.
-type TrieReader struct {
+type Reader struct {
owner common.Hash
reader database.NodeReader
banned map[string]struct{} // Marker to prevent node from being accessed, for tests
}
-// NewTrieReader initializes the trie reader with the given node reader.
-func NewTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*TrieReader, error) {
+// NewReader initializes the trie reader with the given database reader.
+func NewReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*Reader, error) {
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
- return &TrieReader{owner: owner}, nil
+ return &Reader{owner: owner}, nil
}
reader, err := db.NodeReader(stateRoot)
if err != nil {
return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err}
}
- return &TrieReader{owner: owner, reader: reader}, nil
+ return &Reader{owner: owner, reader: reader}, nil
}
// newEmptyReader initializes the pure in-memory reader. All read operations
// should be forbidden and returns the MissingNodeError.
-func newEmptyReader() *TrieReader {
- return &TrieReader{}
+func newEmptyReader() *Reader {
+ return &Reader{}
}
-// node retrieves the rlp-encoded trie node with the provided trie node
+// Node retrieves the rlp-encoded trie node with the provided trie node
// information. An MissingNodeError will be returned in case the node is
// not found or any error is encountered.
//
// Don't modify the returned byte slice since it's not deep-copied and
// still be referenced by database.
-func (r *TrieReader) Node(path []byte, hash common.Hash) ([]byte, error) {
+func (r *Reader) Node(path []byte, hash common.Hash) ([]byte, error) {
// Perform the logics in tests for preventing trie node access.
if r.banned != nil {
if _, ok := r.banned[string(path)]; ok {
diff --git a/trie/verkle.go b/trie/verkle.go
index 4922e957da6..d51867975c4 100644
--- a/trie/verkle.go
+++ b/trie/verkle.go
@@ -41,13 +41,13 @@ var (
type VerkleTrie struct {
root verkle.VerkleNode
cache *utils.PointCache
- reader *TrieReader
+ reader *Reader
tracer *PrevalueTracer
}
// NewVerkleTrie constructs a verkle tree based on the specified root hash.
func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.PointCache) (*VerkleTrie, error) {
- reader, err := NewTrieReader(root, common.Hash{}, db)
+ reader, err := NewReader(root, common.Hash{}, db)
if err != nil {
return nil, err
}
@@ -55,7 +55,7 @@ func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.Poin
root: verkle.New(),
cache: cache,
reader: reader,
- tracer: newPrevalueTracer(),
+ tracer: NewPrevalueTracer(),
}
// Parse the root verkle node if it's not empty.
if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
@@ -326,7 +326,7 @@ func (t *VerkleTrie) Copy() *VerkleTrie {
root: t.root.Copy(),
cache: t.cache,
reader: t.reader,
- tracer: t.tracer.copy(),
+ tracer: t.tracer.Copy(),
}
}
@@ -451,7 +451,7 @@ func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
if err != nil {
return nil, err
}
- t.tracer.put(path, blob)
+ t.tracer.Put(path, blob)
return blob, nil
}
From 4506867106893187739fa9937f2206b744ef35f3 Mon Sep 17 00:00:00 2001
From: Gary Rong
Date: Mon, 1 Sep 2025 17:40:50 +0800
Subject: [PATCH 09/10] trie: remove unused functions
---
trie/utils/binary.go | 68 --------------------------------------------
trie/verkle.go | 4 ---
2 files changed, 72 deletions(-)
delete mode 100644 trie/utils/binary.go
diff --git a/trie/utils/binary.go b/trie/utils/binary.go
deleted file mode 100644
index d50b5d4167e..00000000000
--- a/trie/utils/binary.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2025 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 utils
-
-import (
- "bytes"
- "crypto/sha256"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/holiman/uint256"
-)
-
-var zeroHash = common.Hash{}
-
-func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
- hasher := sha256.New()
- hasher.Write(zeroHash[:12])
- hasher.Write(addr[:])
- hasher.Write(key[:31])
- k := hasher.Sum(nil)
- k[31] = key[31]
- return k
-}
-
-func GetBinaryTreeKeyCodeHash(addr common.Address) []byte {
- var k [32]byte
- k[31] = CodeHashLeafKey
- return GetBinaryTreeKey(addr, k[:])
-}
-
-func GetBinaryTreeKeyStorageSlot(address common.Address, key []byte) []byte {
- var k [32]byte
-
- // Case when the key belongs to the account header
- if bytes.Equal(key[:31], zeroHash[:31]) && key[31] < 64 {
- k[31] = 64 + key[31]
- return GetBinaryTreeKey(address, k[:])
- }
-
- // Set the main storage offset
- // note that the first 64 bytes of the main offset storage
- // are unreachable, which is consistent with the spec and
- // what verkle does.
- k[0] = 1 // 1 << 248
- copy(k[1:], key[:31])
- k[31] = key[31]
-
- return GetBinaryTreeKey(address, k[:])
-}
-
-func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []byte {
- chunkOffset := new(uint256.Int).Add(codeOffset, chunknr).Bytes()
- return GetBinaryTreeKey(address, chunkOffset)
-}
diff --git a/trie/verkle.go b/trie/verkle.go
index d51867975c4..186ac1f642b 100644
--- a/trie/verkle.go
+++ b/trie/verkle.go
@@ -72,10 +72,6 @@ func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.Poin
return t, nil
}
-func (t *VerkleTrie) FlatdbNodeResolver(path []byte) ([]byte, error) {
- return t.reader.Node(path, common.Hash{})
-}
-
// GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value.
func (t *VerkleTrie) GetKey(key []byte) []byte {
From fa5158753fa27e25b4db7e8f516d265227364502 Mon Sep 17 00:00:00 2001
From: Gary Rong
Date: Mon, 1 Sep 2025 18:44:17 +0800
Subject: [PATCH 10/10] trie/bintrie: fix copy
---
trie/bintrie/internal_node.go | 2 --
trie/bintrie/iterator.go | 2 --
trie/bintrie/stem_node.go | 42 +++++++++++++++++------------------
trie/bintrie/trie.go | 4 +++-
4 files changed, 24 insertions(+), 26 deletions(-)
diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go
index 70dc54a93b0..f3ddd1aab02 100644
--- a/trie/bintrie/internal_node.go
+++ b/trie/bintrie/internal_node.go
@@ -128,7 +128,6 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
} else {
child = &bt.right
}
-
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
return bt, err
}
@@ -186,6 +185,5 @@ func (bt *InternalNode) toDot(parent, path string) string {
if bt.right != nil {
ret = fmt.Sprintf("%s%s", ret, bt.right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
}
-
return ret
}
diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go
index 66564778125..a6bab2bcfa9 100644
--- a/trie/bintrie/iterator.go
+++ b/trie/bintrie/iterator.go
@@ -196,7 +196,6 @@ func (it *binaryNodeIterator) LeafKey() []byte {
if !ok {
panic("Leaf() called on an binary node iterator not at a leaf location")
}
-
return leaf.Key(it.stack[len(it.stack)-1].Index - 1)
}
@@ -208,7 +207,6 @@ func (it *binaryNodeIterator) LeafBlob() []byte {
if !ok {
panic("LeafBlob() called on an binary node iterator not at a leaf location")
}
-
return leaf.Values[it.stack[len(it.stack)-1].Index-1]
}
diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go
index 7422175bd0e..50c06c9761e 100644
--- a/trie/bintrie/stem_node.go
+++ b/trie/bintrie/stem_node.go
@@ -43,25 +43,25 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int
if !bytes.Equal(bt.Stem, key[:31]) {
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
- new := &InternalNode{depth: bt.depth}
+ n := &InternalNode{depth: bt.depth}
bt.depth++
var child, other *BinaryNode
if bitStem == 0 {
- new.left = bt
- child = &new.left
- other = &new.right
+ n.left = bt
+ child = &n.left
+ other = &n.right
} else {
- new.right = bt
- child = &new.right
- other = &new.left
+ n.right = bt
+ child = &n.right
+ other = &n.left
}
- bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
+ bitKey := key[n.depth/8] >> (7 - (n.depth % 8)) & 1
if bitKey == bitStem {
var err error
*child, err = (*child).Insert(key, value, nil, depth+1)
if err != nil {
- return new, fmt.Errorf("insert error: %w", err)
+ return n, fmt.Errorf("insert error: %w", err)
}
*other = Empty{}
} else {
@@ -73,7 +73,7 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int
depth: depth + 1,
}
}
- return new, nil
+ return n, nil
}
if len(value) != 32 {
return bt, errors.New("invalid insertion: value length")
@@ -151,35 +151,35 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv
if !bytes.Equal(bt.Stem, key[:31]) {
bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
- new := &InternalNode{depth: bt.depth}
+ n := &InternalNode{depth: bt.depth}
bt.depth++
var child, other *BinaryNode
if bitStem == 0 {
- new.left = bt
- child = &new.left
- other = &new.right
+ n.left = bt
+ child = &n.left
+ other = &n.right
} else {
- new.right = bt
- child = &new.right
- other = &new.left
+ n.right = bt
+ child = &n.right
+ other = &n.left
}
- bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
+ bitKey := key[n.depth/8] >> (7 - (n.depth % 8)) & 1
if bitKey == bitStem {
var err error
*child, err = (*child).InsertValuesAtStem(key, values, nil, depth+1)
if err != nil {
- return new, fmt.Errorf("insert error: %w", err)
+ return n, fmt.Errorf("insert error: %w", err)
}
*other = Empty{}
} else {
*other = &StemNode{
Stem: slices.Clone(key[:31]),
Values: values,
- depth: new.depth + 1,
+ depth: n.depth + 1,
}
}
- return new, nil
+ return n, nil
}
// same stem, just merge the two value lists
diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go
index d0049f4a758..0a8bd325f58 100644
--- a/trie/bintrie/trie.go
+++ b/trie/bintrie/trie.go
@@ -256,7 +256,6 @@ func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
if err != nil {
panic(fmt.Errorf("CollectNodes failed: %v", err))
}
-
// Serialize root commitment form
return t.Hash(), nodeset
}
@@ -283,6 +282,7 @@ func (t *BinaryTrie) Copy() *BinaryTrie {
return &BinaryTrie{
root: t.root.Copy(),
reader: t.reader,
+ tracer: t.tracer.Copy(),
}
}
@@ -325,6 +325,8 @@ func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Has
return nil
}
+// PrefetchAccount attempts to resolve specific accounts from the database
+// to accelerate subsequent trie operations.
func (t *BinaryTrie) PrefetchAccount(addresses []common.Address) error {
for _, addr := range addresses {
if _, err := t.GetAccount(addr); err != nil {