go-ethereum/trie/bintrie/iterator.go
Guillaume Ballet bd4b17907f
trie/bintrie: add eip7864 binary trees and run its tests (#32365)
Implement the binary tree as specified in [eip-7864](https://eips.ethereum.org/EIPS/eip-7864). 

This will gradually replace verkle trees in the codebase. This is only 
running the tests and will not be executed in production, but will help 
me rebase some of my work, so that it doesn't bitrot as much.

---------

Signed-off-by: Guillaume Ballet
Co-authored-by: Parithosh Jayanthi <parithosh.jayanthi@ethereum.org>
Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
2025-09-01 21:06:51 +08:00

261 lines
7.7 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
package bintrie
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/trie"
)
var errIteratorEnd = errors.New("end of iteration")
type binaryNodeIteratorState struct {
Node BinaryNode
Index int
}
type binaryNodeIterator struct {
trie *BinaryTrie
current BinaryNode
lastErr error
stack []binaryNodeIteratorState
}
func newBinaryNodeIterator(t *BinaryTrie, _ []byte) (trie.NodeIterator, error) {
if t.Hash() == zero {
return &binaryNodeIterator{trie: t, lastErr: errIteratorEnd}, nil
}
it := &binaryNodeIterator{trie: t, current: t.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 *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.(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.(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 *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 HashedNode:
// resolve the node
data, err := it.trie.nodeResolver(it.Path(), common.Hash(node))
if err != nil {
panic(err)
}
it.current, err = 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.(*InternalNode).left = it.current
} else {
parent.Node.(*InternalNode).right = it.current
}
return it.Next(descend)
case 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
}
// NodeBlob returns the serialized bytes of the current node.
func (it *binaryNodeIterator) NodeBlob() []byte {
return SerializeNode(it.current)
}
// Leaf returns true iff the current node is a leaf node.
func (it *binaryNodeIterator) Leaf() bool {
_, ok := it.current.(*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.(*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.(*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 {
sn, ok := it.current.(*StemNode)
if !ok {
panic("LeafProof() called on an binary node iterator not at a leaf location")
}
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
// 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(trie.NodeResolver) {
// Not implemented, but should not panic
}