Move BinaryTrie and iterator to the bintrie package and add more coverage (#547)

* refactor package

* add tests
This commit is contained in:
Parithosh Jayanthi 2025-08-13 19:57:33 +02:00 committed by Guillaume Ballet
parent 2656abc317
commit f13dfc5baa
14 changed files with 1619 additions and 102 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

222
trie/bintrie/empty_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}

View file

@ -14,31 +14,35 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie package bintrie
import ( import (
"errors"
"github.com/ethereum/go-ethereum/common" "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 { type binaryNodeIteratorState struct {
Node bintrie.BinaryNode Node BinaryNode
Index int Index int
} }
type binaryNodeIterator struct { type binaryNodeIterator struct {
trie *BinaryTrie trie *BinaryTrie
current bintrie.BinaryNode current BinaryNode
lastErr error lastErr error
stack []binaryNodeIteratorState stack []binaryNodeIteratorState
} }
func newBinaryNodeIterator(trie *BinaryTrie, _ []byte) (NodeIterator, error) { func newBinaryNodeIterator(t *BinaryTrie, _ []byte) (trie.NodeIterator, error) {
if trie.Hash() == zero { if t.Hash() == zero {
return new(nodeIterator), nil 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) // it.err = it.seek(start)
return it, nil return it, nil
} }
@ -59,13 +63,13 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
} }
switch node := it.current.(type) { switch node := it.current.(type) {
case *bintrie.InternalNode: case *InternalNode:
// index: 0 = nothing visited, 1=left visited, 2=right visited // index: 0 = nothing visited, 1=left visited, 2=right visited
context := &it.stack[len(it.stack)-1] context := &it.stack[len(it.stack)-1]
// recurse into both children // recurse into both children
if context.Index == 0 { 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.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
it.current = node.Left it.current = node.Left
return it.Next(descend) return it.Next(descend)
@ -75,7 +79,7 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
} }
if context.Index == 1 { 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.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
it.current = node.Right it.current = node.Right
return it.Next(descend) 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.current = it.stack[len(it.stack)-1].Node
it.stack[len(it.stack)-1].Index++ it.stack[len(it.stack)-1].Index++
return it.Next(descend) return it.Next(descend)
case *bintrie.StemNode: case *StemNode:
// Look for the next non-empty value // Look for the next non-empty value
for i := it.stack[len(it.stack)-1].Index; i < 256; i++ { for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
if node.Values[i] != nil { 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.current = it.stack[len(it.stack)-1].Node
it.stack[len(it.stack)-1].Index++ it.stack[len(it.stack)-1].Index++
return it.Next(descend) return it.Next(descend)
case bintrie.HashedNode: case HashedNode:
// resolve the node // resolve the node
data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node)) data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
if err != nil { if err != nil {
panic(err) panic(err)
} }
it.current, err = bintrie.DeserializeNode(data, len(it.stack)-1) it.current, err = DeserializeNode(data, len(it.stack)-1)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -123,12 +127,12 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
it.stack[len(it.stack)-1].Node = it.current it.stack[len(it.stack)-1].Node = it.current
parent := &it.stack[len(it.stack)-2] parent := &it.stack[len(it.stack)-2]
if parent.Index == 0 { if parent.Index == 0 {
parent.Node.(*bintrie.InternalNode).Left = it.current parent.Node.(*InternalNode).Left = it.current
} else { } else {
parent.Node.(*bintrie.InternalNode).Right = it.current parent.Node.(*InternalNode).Right = it.current
} }
return it.Next(descend) return it.Next(descend)
case bintrie.Empty: case Empty:
// do nothing // do nothing
return false return false
default: default:
@ -179,7 +183,7 @@ func (it *binaryNodeIterator) NodeBlob() []byte {
// Leaf returns true iff the current node is a leaf node. // Leaf returns true iff the current node is a leaf node.
func (it *binaryNodeIterator) Leaf() bool { func (it *binaryNodeIterator) Leaf() bool {
_, ok := it.current.(*bintrie.StemNode) _, ok := it.current.(*StemNode)
return ok return ok
} }
@ -187,7 +191,7 @@ func (it *binaryNodeIterator) Leaf() bool {
// positioned at a leaf. Callers must not retain references to the value after // positioned at a leaf. Callers must not retain references to the value after
// calling Next. // calling Next.
func (it *binaryNodeIterator) LeafKey() []byte { func (it *binaryNodeIterator) LeafKey() []byte {
leaf, ok := it.current.(*bintrie.StemNode) leaf, ok := it.current.(*StemNode)
if !ok { if !ok {
panic("Leaf() called on an binary node iterator not at a leaf location") 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 // is not positioned at a leaf. Callers must not retain references to the value
// after calling Next. // after calling Next.
func (it *binaryNodeIterator) LeafBlob() []byte { func (it *binaryNodeIterator) LeafBlob() []byte {
leaf, ok := it.current.(*bintrie.StemNode) leaf, ok := it.current.(*StemNode)
if !ok { if !ok {
panic("LeafBlob() called on an binary node iterator not at a leaf location") 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 // iterator is not positioned at a leaf. Callers must not retain references
// to the value after calling Next. // to the value after calling Next.
func (it *binaryNodeIterator) LeafProof() [][]byte { func (it *binaryNodeIterator) LeafProof() [][]byte {
_, ok := it.current.(*bintrie.StemNode) _, ok := it.current.(*StemNode)
if !ok { if !ok {
panic("LeafProof() called on an binary node iterator not at a leaf location") 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 // 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 // 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. // 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 // Not implemented, but should not panic
} }

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie package bintrie
import ( import (
"testing" "testing"
@ -22,9 +22,23 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "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" "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) { func TestBinaryIterator(t *testing.T) {
trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)) trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme))
if err != nil { if err != nil {

View file

@ -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 <http://www.gnu.org/licenses/>.
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])
}
}

View file

@ -14,58 +14,58 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie package bintrie
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "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/trienode"
"github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-ethereum/triedb/database" "github.com/ethereum/go-ethereum/triedb/database"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
// zero is the zero value for a 32-byte array. var errInvalidRootType = errors.New("invalid root type")
var zero [32]byte
// NewBinaryNode creates a new empty binary trie // NewBinaryNode creates a new empty binary trie
func NewBinaryNode() bintrie.BinaryNode { func NewBinaryNode() BinaryNode {
return bintrie.Empty{} return Empty{}
} }
// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie // BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
// interface so that Verkle trees can be reused verbatim. // interface so that Verkle trees can be reused verbatim.
type BinaryTrie struct { type BinaryTrie struct {
root bintrie.BinaryNode root BinaryNode
reader *trieReader reader *trie.TrieReader
} }
// ToDot converts the binary trie to a DOT language representation. Useful for debugging. // ToDot converts the binary trie to a DOT language representation. Useful for debugging.
func (trie *BinaryTrie) ToDot() string { func (t *BinaryTrie) ToDot() string {
trie.root.Hash() t.root.Hash()
return bintrie.ToDot(trie.root) return ToDot(t.root)
} }
// NewBinaryTrie creates a new binary trie. // NewBinaryTrie creates a new binary trie.
func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) { 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 { if err != nil {
return nil, err return nil, err
} }
// Parse the root verkle node if it's not empty. // Parse the root verkle node if it's not empty.
node := NewBinaryNode() node := NewBinaryNode()
if root != types.EmptyVerkleHash && root != types.EmptyRootHash { if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
blob, err := reader.node(nil, common.Hash{}) blob, err := reader.Node(nil, common.Hash{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
node, err = bintrie.DeserializeNode(blob, 0) node, err = DeserializeNode(blob, 0)
if err != nil { if err != nil {
return nil, err 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. // 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 // empty nodes will be serialized as common.Hash{}, so capture
// this special use case. // this special use case.
if hash == (common.Hash{}) { if hash == (common.Hash{}) {
return nil, nil // empty node 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 // GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value. // to store a value.
func (trie *BinaryTrie) GetKey(key []byte) []byte { func (t *BinaryTrie) GetKey(key []byte) []byte {
return key return key
} }
// Get returns the value for key stored in the trie. The value bytes must // 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 // not be modified by the caller. If a node was not found in the database, a
// trie.MissingNodeError is returned. // trie.MissingNodeError is returned.
func (trie *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
return trie.root.Get(utils.GetBinaryTreeKey(addr, key), trie.FlatdbNodeResolver) return t.root.Get(utils.GetBinaryTreeKey(addr, key), t.FlatdbNodeResolver)
} }
// GetWithHashedKey returns the value, assuming that the key has already // GetWithHashedKey returns the value, assuming that the key has already
// been hashed. // been hashed.
func (trie *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) { func (t *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
return trie.root.Get(key, trie.FlatdbNodeResolver) return t.root.Get(key, t.FlatdbNodeResolver)
} }
// GetAccount returns the account information for the given address. // 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{} acc := &types.StateAccount{}
versionkey := utils.GetBinaryTreeKey(addr, zero[:]) versionkey := utils.GetBinaryTreeKey(addr, zero[:])
var ( var (
values [][]byte values [][]byte
err error err error
) )
switch r := trie.root.(type) { switch r := t.root.(type) {
case *bintrie.InternalNode: case *InternalNode:
values, err = r.GetValuesAtStem(versionkey[:31], trie.FlatdbNodeResolver) values, err = r.GetValuesAtStem(versionkey[:31], t.FlatdbNodeResolver)
case *bintrie.StemNode: case *StemNode:
values = r.Values values = r.Values
case bintrie.Empty: case Empty:
return nil, nil return nil, nil
default: default:
// This will cover HashedNode but that should be fine since the // 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. // 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 ( var (
err error err error
basicData [32]byte basicData [32]byte
values = make([][]byte, bintrie.NodeWidth) values = make([][]byte, NodeWidth)
stem = utils.GetBinaryTreeKey(addr, zero[:]) 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.BasicDataLeafKey] = basicData[:]
values[utils.CodeHashLeafKey] = acc.CodeHash[:] 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 return err
} }
// UpdateStem updates the values for the given stem key. // 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 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 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 // 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 // by the caller while they are stored in the trie. If a node was not found in the
// database, a trie.MissingNodeError is returned. // 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) k := utils.GetBinaryTreeKeyStorageSlot(address, key)
var v [32]byte var v [32]byte
if len(value) >= 32 { if len(value) >= 32 {
@ -206,46 +206,46 @@ func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte)
} else { } else {
copy(v[32-len(value):], value[:]) 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 { if err != nil {
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err) return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
} }
trie.root = root t.root = root
return nil return nil
} }
// DeleteAccount is a no-op as it is disabled in stateless. // 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 return nil
} }
// Delete removes any existing value for key from the trie. If a node was not // Delete removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned. // 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) k := utils.GetBinaryTreeKey(addr, key)
var zero [32]byte 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 { if err != nil {
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err) return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
} }
trie.root = root t.root = root
return nil return nil
} }
// Hash returns the root hash of the trie. It does not write to the database and // 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. // can be used even if the trie doesn't have one.
func (trie *BinaryTrie) Hash() common.Hash { func (t *BinaryTrie) Hash() common.Hash {
return trie.root.Hash() return t.root.Hash()
} }
// Commit writes all nodes to the trie's memory database, tracking the internal // Commit writes all nodes to the trie's memory database, tracking the internal
// and external (for account tries) references. // and external (for account tries) references.
func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) { func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
root := trie.root.(*bintrie.InternalNode) root := t.root.(*InternalNode)
nodeset := trienode.NewNodeSet(common.Hash{}) nodeset := trienode.NewNodeSet(common.Hash{})
err := root.CollectNodes(nil, func(path []byte, node bintrie.BinaryNode) { err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
serialized := bintrie.SerializeNode(node) serialized := SerializeNode(node)
nodeset.AddNode(path, trienode.New(common.Hash{}, serialized)) nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
}) })
if err != nil { if err != nil {
@ -253,13 +253,13 @@ func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
} }
// Serialize root commitment form // 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 // NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. // starts at the key after the given start key.
func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) { func (t *BinaryTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) {
return newBinaryNodeIterator(trie, nil) return newBinaryNodeIterator(t, nil)
} }
// Prove constructs a Merkle proof for key. The result contains all encoded nodes // 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 // 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 // nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key. // 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") panic("not implemented")
} }
// Copy creates a deep copy of the trie. // Copy creates a deep copy of the trie.
func (trie *BinaryTrie) Copy() *BinaryTrie { func (t *BinaryTrie) Copy() *BinaryTrie {
return &BinaryTrie{ return &BinaryTrie{
root: trie.root.Copy(), root: t.root.Copy(),
reader: trie.reader, reader: t.reader,
} }
} }
// IsVerkle returns true if the trie is a Verkle tree. // 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 // 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 // behavior and basic structure, so for all intents and purposes, it can be
// treated as such. Rename this when verkle gets removed. // 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 // 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 ( var (
chunks = ChunkifyCode(code) chunks = trie.ChunkifyCode(code)
values [][]byte values [][]byte
key []byte key []byte
err error 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 { for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
groupOffset := (chunknr + 128) % 256 groupOffset := (chunknr + 128) % 256
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ { 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 var offset [32]byte
binary.LittleEndian.PutUint64(offset[24:], chunknr+128) binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
key = utils.GetBinaryTreeKey(addr, offset[:]) key = utils.GetBinaryTreeKey(addr, offset[:])
@ -308,7 +308,7 @@ func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.
values[groupOffset] = chunks[i : i+32] values[groupOffset] = chunks[i : i+32]
if groupOffset == 255 || len(chunks)-i <= 32 { if groupOffset == 255 || len(chunks)-i <= 32 {
err = trie.UpdateStem(key[:31], values) err = t.UpdateStem(key[:31], values)
if err != nil { if err != nil {
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err) return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie package bintrie
import ( import (
"bytes" "bytes"
@ -22,7 +22,6 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/trie/bintrie"
) )
var ( var (
@ -152,7 +151,7 @@ func TestInsertDuplicateKey(t *testing.T) {
t.Fatal("invalid height") t.Fatal("invalid height")
} }
// Verify that the value is updated // 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") t.Fatal("invalid height")
} }
} }

View file

@ -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 // 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 // all loaded nodes won't be linked to trie at all and track nodes
// may lead to out-of-memory issue. // 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 { if err != nil {
return nil, err 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 // 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 // all loaded nodes won't be linked to trie at all and track nodes
// may lead to out-of-memory issue. // 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 { func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {

View file

@ -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 // 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 // all loaded nodes won't be linked to trie at all and track nodes
// may lead to out-of-memory issue. // 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 { if err != nil {
log.Error("Unhandled trie error in Trie.Prove", "err", err) log.Error("Unhandled trie error in Trie.Prove", "err", err)
return err return err

View file

@ -55,7 +55,7 @@ type Trie struct {
uncommitted int uncommitted int
// reader is the handler trie can retrieve nodes from. // reader is the handler trie can retrieve nodes from.
reader *trieReader reader *TrieReader
// Various tracers for capturing the modifications to trie // Various tracers for capturing the modifications to trie
opTracer *opTracer opTracer *opTracer
@ -88,7 +88,7 @@ func (t *Trie) Copy() *Trie {
// empty, otherwise, the root node must be present in database or returns // empty, otherwise, the root node must be present in database or returns
// a MissingNodeError if not. // a MissingNodeError if not.
func New(id *ID, db database.NodeDatabase) (*Trie, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -289,7 +289,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
if hash == nil { if hash == nil {
return nil, origNode, 0, errors.New("non-consensus node") 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 return blob, origNode, 1, err
} }
// Path still needs to be traversed, descend into children // 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 // 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. // 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -22,30 +22,30 @@ import (
"github.com/ethereum/go-ethereum/triedb/database" "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. // for concurrent usage.
type trieReader struct { type TrieReader struct {
owner common.Hash owner common.Hash
reader database.NodeReader reader database.NodeReader
banned map[string]struct{} // Marker to prevent node from being accessed, for tests banned map[string]struct{} // Marker to prevent node from being accessed, for tests
} }
// newTrieReader initializes the trie reader with the given node reader. // NewTrieReader initializes the trie reader with the given node reader.
func newTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*trieReader, error) { func NewTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*TrieReader, error) {
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash { if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
return &trieReader{owner: owner}, nil return &TrieReader{owner: owner}, nil
} }
reader, err := db.NodeReader(stateRoot) reader, err := db.NodeReader(stateRoot)
if err != nil { if err != nil {
return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err} 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 // newEmptyReader initializes the pure in-memory reader. All read operations
// should be forbidden and returns the MissingNodeError. // should be forbidden and returns the MissingNodeError.
func newEmptyReader() *trieReader { func newEmptyReader() *TrieReader {
return &trieReader{} return &TrieReader{}
} }
// node retrieves the rlp-encoded trie node with the provided trie node // 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 // Don't modify the returned byte slice since it's not deep-copied and
// still be referenced by database. // 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. // Perform the logics in tests for preventing trie node access.
if r.banned != nil { if r.banned != nil {
if _, ok := r.banned[string(path)]; ok { if _, ok := r.banned[string(path)]; ok {

View file

@ -41,13 +41,13 @@ var (
type VerkleTrie struct { type VerkleTrie struct {
root verkle.VerkleNode root verkle.VerkleNode
cache *utils.PointCache cache *utils.PointCache
reader *trieReader reader *TrieReader
tracer *prevalueTracer tracer *prevalueTracer
} }
// NewVerkleTrie constructs a verkle tree based on the specified root hash. // NewVerkleTrie constructs a verkle tree based on the specified root hash.
func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.PointCache) (*VerkleTrie, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -72,6 +72,10 @@ func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.Poin
return t, nil 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 // GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value. // to store a value.
func (t *VerkleTrie) GetKey(key []byte) []byte { func (t *VerkleTrie) GetKey(key []byte) []byte {
@ -443,7 +447,7 @@ func (t *VerkleTrie) ToDot() string {
} }
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) { 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 { if err != nil {
return nil, err return nil, err
} }