mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 05:36:46 +00:00
Move BinaryTrie and iterator to the bintrie package and add more coverage (#547)
* refactor package * add tests
This commit is contained in:
parent
2656abc317
commit
f13dfc5baa
14 changed files with 1619 additions and 102 deletions
269
trie/bintrie/binary_node_test.go
Normal file
269
trie/bintrie/binary_node_test.go
Normal 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
222
trie/bintrie/empty_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
174
trie/bintrie/hashed_node_test.go
Normal file
174
trie/bintrie/hashed_node_test.go
Normal 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
|
||||
}
|
||||
458
trie/bintrie/internal_node_test.go
Normal file
458
trie/bintrie/internal_node_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 {
|
||||
373
trie/bintrie/stem_node_test.go
Normal file
373
trie/bintrie/stem_node_test.go
Normal 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue