From f13dfc5baa6d8866ba0c1f3ced994e9be8338158 Mon Sep 17 00:00:00 2001 From: Parithosh Jayanthi Date: Wed, 13 Aug 2025 19:57:33 +0200 Subject: [PATCH] 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 0000000000..5db6e111f0 --- /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 0000000000..a3c3681cad --- /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 0000000000..dbc6a7a244 --- /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 0000000000..142f1e95c1 --- /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 e4799d207a..94ce3895df 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 01a4373cd2..8773e9e0c5 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 0000000000..47dda38f59 --- /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 2cad0999c7..19543bf45c 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 082ca9b303..e6cf795ee5 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 e6fedf2430..80298ce48f 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 f3ed417094..f9bc674d52 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 98cf751f47..90b06b51df 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 a42cdb0cf9..b776709d25 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 e00ea21602..8233aded38 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 }