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
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package trie
|
package bintrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errIteratorEnd = errors.New("end of iteration")
|
||||||
|
|
||||||
type binaryNodeIteratorState struct {
|
type binaryNodeIteratorState struct {
|
||||||
Node bintrie.BinaryNode
|
Node BinaryNode
|
||||||
Index int
|
Index int
|
||||||
}
|
}
|
||||||
|
|
||||||
type binaryNodeIterator struct {
|
type binaryNodeIterator struct {
|
||||||
trie *BinaryTrie
|
trie *BinaryTrie
|
||||||
current bintrie.BinaryNode
|
current BinaryNode
|
||||||
lastErr error
|
lastErr error
|
||||||
|
|
||||||
stack []binaryNodeIteratorState
|
stack []binaryNodeIteratorState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBinaryNodeIterator(trie *BinaryTrie, _ []byte) (NodeIterator, error) {
|
func newBinaryNodeIterator(t *BinaryTrie, _ []byte) (trie.NodeIterator, error) {
|
||||||
if trie.Hash() == zero {
|
if t.Hash() == zero {
|
||||||
return new(nodeIterator), nil
|
return &binaryNodeIterator{trie: t, lastErr: errIteratorEnd}, nil
|
||||||
}
|
}
|
||||||
it := &binaryNodeIterator{trie: trie, current: trie.root}
|
it := &binaryNodeIterator{trie: t, current: t.root}
|
||||||
// it.err = it.seek(start)
|
// it.err = it.seek(start)
|
||||||
return it, nil
|
return it, nil
|
||||||
}
|
}
|
||||||
|
|
@ -59,13 +63,13 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch node := it.current.(type) {
|
switch node := it.current.(type) {
|
||||||
case *bintrie.InternalNode:
|
case *InternalNode:
|
||||||
// index: 0 = nothing visited, 1=left visited, 2=right visited
|
// index: 0 = nothing visited, 1=left visited, 2=right visited
|
||||||
context := &it.stack[len(it.stack)-1]
|
context := &it.stack[len(it.stack)-1]
|
||||||
|
|
||||||
// recurse into both children
|
// recurse into both children
|
||||||
if context.Index == 0 {
|
if context.Index == 0 {
|
||||||
if _, isempty := node.Left.(bintrie.Empty); node.Left != nil && !isempty {
|
if _, isempty := node.Left.(Empty); node.Left != nil && !isempty {
|
||||||
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
|
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left})
|
||||||
it.current = node.Left
|
it.current = node.Left
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
|
|
@ -75,7 +79,7 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.Index == 1 {
|
if context.Index == 1 {
|
||||||
if _, isempty := node.Right.(bintrie.Empty); node.Right != nil && !isempty {
|
if _, isempty := node.Right.(Empty); node.Right != nil && !isempty {
|
||||||
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
|
it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right})
|
||||||
it.current = node.Right
|
it.current = node.Right
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
|
|
@ -94,7 +98,7 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
it.current = it.stack[len(it.stack)-1].Node
|
it.current = it.stack[len(it.stack)-1].Node
|
||||||
it.stack[len(it.stack)-1].Index++
|
it.stack[len(it.stack)-1].Index++
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
case *bintrie.StemNode:
|
case *StemNode:
|
||||||
// Look for the next non-empty value
|
// Look for the next non-empty value
|
||||||
for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
|
for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
|
||||||
if node.Values[i] != nil {
|
if node.Values[i] != nil {
|
||||||
|
|
@ -108,13 +112,13 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
it.current = it.stack[len(it.stack)-1].Node
|
it.current = it.stack[len(it.stack)-1].Node
|
||||||
it.stack[len(it.stack)-1].Index++
|
it.stack[len(it.stack)-1].Index++
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
case bintrie.HashedNode:
|
case HashedNode:
|
||||||
// resolve the node
|
// resolve the node
|
||||||
data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
|
data, err := it.trie.FlatdbNodeResolver(it.Path(), common.Hash(node))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
it.current, err = bintrie.DeserializeNode(data, len(it.stack)-1)
|
it.current, err = DeserializeNode(data, len(it.stack)-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -123,12 +127,12 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
it.stack[len(it.stack)-1].Node = it.current
|
it.stack[len(it.stack)-1].Node = it.current
|
||||||
parent := &it.stack[len(it.stack)-2]
|
parent := &it.stack[len(it.stack)-2]
|
||||||
if parent.Index == 0 {
|
if parent.Index == 0 {
|
||||||
parent.Node.(*bintrie.InternalNode).Left = it.current
|
parent.Node.(*InternalNode).Left = it.current
|
||||||
} else {
|
} else {
|
||||||
parent.Node.(*bintrie.InternalNode).Right = it.current
|
parent.Node.(*InternalNode).Right = it.current
|
||||||
}
|
}
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
case bintrie.Empty:
|
case Empty:
|
||||||
// do nothing
|
// do nothing
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
|
|
@ -179,7 +183,7 @@ func (it *binaryNodeIterator) NodeBlob() []byte {
|
||||||
|
|
||||||
// Leaf returns true iff the current node is a leaf node.
|
// Leaf returns true iff the current node is a leaf node.
|
||||||
func (it *binaryNodeIterator) Leaf() bool {
|
func (it *binaryNodeIterator) Leaf() bool {
|
||||||
_, ok := it.current.(*bintrie.StemNode)
|
_, ok := it.current.(*StemNode)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +191,7 @@ func (it *binaryNodeIterator) Leaf() bool {
|
||||||
// positioned at a leaf. Callers must not retain references to the value after
|
// positioned at a leaf. Callers must not retain references to the value after
|
||||||
// calling Next.
|
// calling Next.
|
||||||
func (it *binaryNodeIterator) LeafKey() []byte {
|
func (it *binaryNodeIterator) LeafKey() []byte {
|
||||||
leaf, ok := it.current.(*bintrie.StemNode)
|
leaf, ok := it.current.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("Leaf() called on an binary node iterator not at a leaf location")
|
panic("Leaf() called on an binary node iterator not at a leaf location")
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +203,7 @@ func (it *binaryNodeIterator) LeafKey() []byte {
|
||||||
// is not positioned at a leaf. Callers must not retain references to the value
|
// is not positioned at a leaf. Callers must not retain references to the value
|
||||||
// after calling Next.
|
// after calling Next.
|
||||||
func (it *binaryNodeIterator) LeafBlob() []byte {
|
func (it *binaryNodeIterator) LeafBlob() []byte {
|
||||||
leaf, ok := it.current.(*bintrie.StemNode)
|
leaf, ok := it.current.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("LeafBlob() called on an binary node iterator not at a leaf location")
|
panic("LeafBlob() called on an binary node iterator not at a leaf location")
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +215,7 @@ func (it *binaryNodeIterator) LeafBlob() []byte {
|
||||||
// iterator is not positioned at a leaf. Callers must not retain references
|
// iterator is not positioned at a leaf. Callers must not retain references
|
||||||
// to the value after calling Next.
|
// to the value after calling Next.
|
||||||
func (it *binaryNodeIterator) LeafProof() [][]byte {
|
func (it *binaryNodeIterator) LeafProof() [][]byte {
|
||||||
_, ok := it.current.(*bintrie.StemNode)
|
_, ok := it.current.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("LeafProof() called on an binary node iterator not at a leaf location")
|
panic("LeafProof() called on an binary node iterator not at a leaf location")
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +235,6 @@ func (it *binaryNodeIterator) LeafProof() [][]byte {
|
||||||
// Before adding a similar mechanism to any other place in Geth, consider
|
// Before adding a similar mechanism to any other place in Geth, consider
|
||||||
// making trie.Database an interface and wrapping at that level. It's a huge
|
// making trie.Database an interface and wrapping at that level. It's a huge
|
||||||
// refactor, but it could be worth it if another occurrence arises.
|
// refactor, but it could be worth it if another occurrence arises.
|
||||||
func (it *binaryNodeIterator) AddResolver(NodeResolver) {
|
func (it *binaryNodeIterator) AddResolver(trie.NodeResolver) {
|
||||||
// Not implemented, but should not panic
|
// Not implemented, but should not panic
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package trie
|
package bintrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -22,9 +22,23 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func newTestDatabase(diskdb ethdb.Database, scheme string) *triedb.Database {
|
||||||
|
config := &triedb.Config{Preimages: true}
|
||||||
|
if scheme == rawdb.HashScheme {
|
||||||
|
config.HashDB = &hashdb.Config{CleanCacheSize: 0}
|
||||||
|
} else {
|
||||||
|
config.PathDB = &pathdb.Config{TrieCleanSize: 0, StateCleanSize: 0}
|
||||||
|
}
|
||||||
|
return triedb.NewDatabase(diskdb, config)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBinaryIterator(t *testing.T) {
|
func TestBinaryIterator(t *testing.T) {
|
||||||
trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme))
|
trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package trie
|
package bintrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
"github.com/ethereum/go-ethereum/trie/utils"
|
||||||
"github.com/ethereum/go-ethereum/triedb/database"
|
"github.com/ethereum/go-ethereum/triedb/database"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// zero is the zero value for a 32-byte array.
|
var errInvalidRootType = errors.New("invalid root type")
|
||||||
var zero [32]byte
|
|
||||||
|
|
||||||
// NewBinaryNode creates a new empty binary trie
|
// NewBinaryNode creates a new empty binary trie
|
||||||
func NewBinaryNode() bintrie.BinaryNode {
|
func NewBinaryNode() BinaryNode {
|
||||||
return bintrie.Empty{}
|
return Empty{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
|
// BinaryTrie is a wrapper around VerkleNode that implements the trie.Trie
|
||||||
// interface so that Verkle trees can be reused verbatim.
|
// interface so that Verkle trees can be reused verbatim.
|
||||||
type BinaryTrie struct {
|
type BinaryTrie struct {
|
||||||
root bintrie.BinaryNode
|
root BinaryNode
|
||||||
reader *trieReader
|
reader *trie.TrieReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
|
||||||
func (trie *BinaryTrie) ToDot() string {
|
func (t *BinaryTrie) ToDot() string {
|
||||||
trie.root.Hash()
|
t.root.Hash()
|
||||||
return bintrie.ToDot(trie.root)
|
return ToDot(t.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBinaryTrie creates a new binary trie.
|
// NewBinaryTrie creates a new binary trie.
|
||||||
func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) {
|
func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, error) {
|
||||||
reader, err := newTrieReader(root, common.Hash{}, db)
|
reader, err := trie.NewTrieReader(root, common.Hash{}, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Parse the root verkle node if it's not empty.
|
// Parse the root verkle node if it's not empty.
|
||||||
node := NewBinaryNode()
|
node := NewBinaryNode()
|
||||||
if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
|
if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
|
||||||
blob, err := reader.node(nil, common.Hash{})
|
blob, err := reader.Node(nil, common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
node, err = bintrie.DeserializeNode(blob, 0)
|
node, err = DeserializeNode(blob, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -77,48 +77,48 @@ func NewBinaryTrie(root common.Hash, db database.NodeDatabase) (*BinaryTrie, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatdbNodeResolver is a node resolver that reads nodes from the flatdb.
|
// FlatdbNodeResolver is a node resolver that reads nodes from the flatdb.
|
||||||
func (trie *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
|
func (t *BinaryTrie) FlatdbNodeResolver(path []byte, hash common.Hash) ([]byte, error) {
|
||||||
// empty nodes will be serialized as common.Hash{}, so capture
|
// empty nodes will be serialized as common.Hash{}, so capture
|
||||||
// this special use case.
|
// this special use case.
|
||||||
if hash == (common.Hash{}) {
|
if hash == (common.Hash{}) {
|
||||||
return nil, nil // empty node
|
return nil, nil // empty node
|
||||||
}
|
}
|
||||||
return trie.reader.node(path, hash)
|
return t.reader.Node(path, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
||||||
// to store a value.
|
// to store a value.
|
||||||
func (trie *BinaryTrie) GetKey(key []byte) []byte {
|
func (t *BinaryTrie) GetKey(key []byte) []byte {
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value for key stored in the trie. The value bytes must
|
// Get returns the value for key stored in the trie. The value bytes must
|
||||||
// not be modified by the caller. If a node was not found in the database, a
|
// not be modified by the caller. If a node was not found in the database, a
|
||||||
// trie.MissingNodeError is returned.
|
// trie.MissingNodeError is returned.
|
||||||
func (trie *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
|
func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
|
||||||
return trie.root.Get(utils.GetBinaryTreeKey(addr, key), trie.FlatdbNodeResolver)
|
return t.root.Get(utils.GetBinaryTreeKey(addr, key), t.FlatdbNodeResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetWithHashedKey returns the value, assuming that the key has already
|
// GetWithHashedKey returns the value, assuming that the key has already
|
||||||
// been hashed.
|
// been hashed.
|
||||||
func (trie *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
|
func (t *BinaryTrie) GetWithHashedKey(key []byte) ([]byte, error) {
|
||||||
return trie.root.Get(key, trie.FlatdbNodeResolver)
|
return t.root.Get(key, t.FlatdbNodeResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns the account information for the given address.
|
// GetAccount returns the account information for the given address.
|
||||||
func (trie *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
|
func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
|
||||||
acc := &types.StateAccount{}
|
acc := &types.StateAccount{}
|
||||||
versionkey := utils.GetBinaryTreeKey(addr, zero[:])
|
versionkey := utils.GetBinaryTreeKey(addr, zero[:])
|
||||||
var (
|
var (
|
||||||
values [][]byte
|
values [][]byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
switch r := trie.root.(type) {
|
switch r := t.root.(type) {
|
||||||
case *bintrie.InternalNode:
|
case *InternalNode:
|
||||||
values, err = r.GetValuesAtStem(versionkey[:31], trie.FlatdbNodeResolver)
|
values, err = r.GetValuesAtStem(versionkey[:31], t.FlatdbNodeResolver)
|
||||||
case *bintrie.StemNode:
|
case *StemNode:
|
||||||
values = r.Values
|
values = r.Values
|
||||||
case bintrie.Empty:
|
case Empty:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
default:
|
default:
|
||||||
// This will cover HashedNode but that should be fine since the
|
// This will cover HashedNode but that should be fine since the
|
||||||
|
|
@ -160,11 +160,11 @@ func (trie *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, er
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateAccount updates the account information for the given address.
|
// UpdateAccount updates the account information for the given address.
|
||||||
func (trie *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
|
func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
basicData [32]byte
|
basicData [32]byte
|
||||||
values = make([][]byte, bintrie.NodeWidth)
|
values = make([][]byte, NodeWidth)
|
||||||
stem = utils.GetBinaryTreeKey(addr, zero[:])
|
stem = utils.GetBinaryTreeKey(addr, zero[:])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -183,14 +183,14 @@ func (trie *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccou
|
||||||
values[utils.BasicDataLeafKey] = basicData[:]
|
values[utils.BasicDataLeafKey] = basicData[:]
|
||||||
values[utils.CodeHashLeafKey] = acc.CodeHash[:]
|
values[utils.CodeHashLeafKey] = acc.CodeHash[:]
|
||||||
|
|
||||||
trie.root, err = trie.root.InsertValuesAtStem(stem, values, trie.FlatdbNodeResolver, 0)
|
t.root, err = t.root.InsertValuesAtStem(stem, values, t.FlatdbNodeResolver, 0)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStem updates the values for the given stem key.
|
// UpdateStem updates the values for the given stem key.
|
||||||
func (trie *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
|
func (t *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
|
||||||
var err error
|
var err error
|
||||||
trie.root, err = trie.root.InsertValuesAtStem(key, values, trie.FlatdbNodeResolver, 0)
|
t.root, err = t.root.InsertValuesAtStem(key, values, t.FlatdbNodeResolver, 0)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,7 +198,7 @@ func (trie *BinaryTrie) UpdateStem(key []byte, values [][]byte) error {
|
||||||
// existing value is deleted from the trie. The value bytes must not be modified
|
// existing value is deleted from the trie. The value bytes must not be modified
|
||||||
// by the caller while they are stored in the trie. If a node was not found in the
|
// by the caller while they are stored in the trie. If a node was not found in the
|
||||||
// database, a trie.MissingNodeError is returned.
|
// database, a trie.MissingNodeError is returned.
|
||||||
func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
func (t *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
||||||
k := utils.GetBinaryTreeKeyStorageSlot(address, key)
|
k := utils.GetBinaryTreeKeyStorageSlot(address, key)
|
||||||
var v [32]byte
|
var v [32]byte
|
||||||
if len(value) >= 32 {
|
if len(value) >= 32 {
|
||||||
|
|
@ -206,46 +206,46 @@ func (trie *BinaryTrie) UpdateStorage(address common.Address, key, value []byte)
|
||||||
} else {
|
} else {
|
||||||
copy(v[32-len(value):], value[:])
|
copy(v[32-len(value):], value[:])
|
||||||
}
|
}
|
||||||
root, err := trie.root.Insert(k, v[:], trie.FlatdbNodeResolver)
|
root, err := t.root.Insert(k, v[:], t.FlatdbNodeResolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
|
return fmt.Errorf("UpdateStorage (%x) error: %v", address, err)
|
||||||
}
|
}
|
||||||
trie.root = root
|
t.root = root
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAccount is a no-op as it is disabled in stateless.
|
// DeleteAccount is a no-op as it is disabled in stateless.
|
||||||
func (trie *BinaryTrie) DeleteAccount(addr common.Address) error {
|
func (t *BinaryTrie) DeleteAccount(addr common.Address) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes any existing value for key from the trie. If a node was not
|
// Delete removes any existing value for key from the trie. If a node was not
|
||||||
// found in the database, a trie.MissingNodeError is returned.
|
// found in the database, a trie.MissingNodeError is returned.
|
||||||
func (trie *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
|
func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error {
|
||||||
k := utils.GetBinaryTreeKey(addr, key)
|
k := utils.GetBinaryTreeKey(addr, key)
|
||||||
var zero [32]byte
|
var zero [32]byte
|
||||||
root, err := trie.root.Insert(k, zero[:], trie.FlatdbNodeResolver)
|
root, err := t.root.Insert(k, zero[:], t.FlatdbNodeResolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
|
return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err)
|
||||||
}
|
}
|
||||||
trie.root = root
|
t.root = root
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the root hash of the trie. It does not write to the database and
|
// Hash returns the root hash of the trie. It does not write to the database and
|
||||||
// can be used even if the trie doesn't have one.
|
// can be used even if the trie doesn't have one.
|
||||||
func (trie *BinaryTrie) Hash() common.Hash {
|
func (t *BinaryTrie) Hash() common.Hash {
|
||||||
return trie.root.Hash()
|
return t.root.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit writes all nodes to the trie's memory database, tracking the internal
|
// Commit writes all nodes to the trie's memory database, tracking the internal
|
||||||
// and external (for account tries) references.
|
// and external (for account tries) references.
|
||||||
func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
||||||
root := trie.root.(*bintrie.InternalNode)
|
root := t.root.(*InternalNode)
|
||||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||||
|
|
||||||
err := root.CollectNodes(nil, func(path []byte, node bintrie.BinaryNode) {
|
err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
|
||||||
serialized := bintrie.SerializeNode(node)
|
serialized := SerializeNode(node)
|
||||||
nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
|
nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -253,13 +253,13 @@ func (trie *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize root commitment form
|
// Serialize root commitment form
|
||||||
return trie.Hash(), nodeset, nil
|
return t.Hash(), nodeset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
||||||
// starts at the key after the given start key.
|
// starts at the key after the given start key.
|
||||||
func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
|
func (t *BinaryTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) {
|
||||||
return newBinaryNodeIterator(trie, nil)
|
return newBinaryNodeIterator(t, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prove constructs a Merkle proof for key. The result contains all encoded nodes
|
// Prove constructs a Merkle proof for key. The result contains all encoded nodes
|
||||||
|
|
@ -269,20 +269,20 @@ func (trie *BinaryTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
|
||||||
// If the trie does not contain a value for key, the returned proof contains all
|
// If the trie does not contain a value for key, the returned proof contains all
|
||||||
// nodes of the longest existing prefix of the key (at least the root), ending
|
// nodes of the longest existing prefix of the key (at least the root), ending
|
||||||
// with the node that proves the absence of the key.
|
// with the node that proves the absence of the key.
|
||||||
func (trie *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
func (t *BinaryTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy creates a deep copy of the trie.
|
// Copy creates a deep copy of the trie.
|
||||||
func (trie *BinaryTrie) Copy() *BinaryTrie {
|
func (t *BinaryTrie) Copy() *BinaryTrie {
|
||||||
return &BinaryTrie{
|
return &BinaryTrie{
|
||||||
root: trie.root.Copy(),
|
root: t.root.Copy(),
|
||||||
reader: trie.reader,
|
reader: t.reader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVerkle returns true if the trie is a Verkle tree.
|
// IsVerkle returns true if the trie is a Verkle tree.
|
||||||
func (trie *BinaryTrie) IsVerkle() bool {
|
func (t *BinaryTrie) IsVerkle() bool {
|
||||||
// TODO @gballet This is technically NOT a verkle tree, but it has the same
|
// TODO @gballet This is technically NOT a verkle tree, but it has the same
|
||||||
// behavior and basic structure, so for all intents and purposes, it can be
|
// behavior and basic structure, so for all intents and purposes, it can be
|
||||||
// treated as such. Rename this when verkle gets removed.
|
// treated as such. Rename this when verkle gets removed.
|
||||||
|
|
@ -290,9 +290,9 @@ func (trie *BinaryTrie) IsVerkle() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: the basic data leaf needs to have been previously created for this to work
|
// Note: the basic data leaf needs to have been previously created for this to work
|
||||||
func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
|
func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
|
||||||
var (
|
var (
|
||||||
chunks = ChunkifyCode(code)
|
chunks = trie.ChunkifyCode(code)
|
||||||
values [][]byte
|
values [][]byte
|
||||||
key []byte
|
key []byte
|
||||||
err error
|
err error
|
||||||
|
|
@ -300,7 +300,7 @@ func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.
|
||||||
for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
|
for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
|
||||||
groupOffset := (chunknr + 128) % 256
|
groupOffset := (chunknr + 128) % 256
|
||||||
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
|
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
|
||||||
values = make([][]byte, bintrie.NodeWidth)
|
values = make([][]byte, NodeWidth)
|
||||||
var offset [32]byte
|
var offset [32]byte
|
||||||
binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
|
binary.LittleEndian.PutUint64(offset[24:], chunknr+128)
|
||||||
key = utils.GetBinaryTreeKey(addr, offset[:])
|
key = utils.GetBinaryTreeKey(addr, offset[:])
|
||||||
|
|
@ -308,7 +308,7 @@ func (trie *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.
|
||||||
values[groupOffset] = chunks[i : i+32]
|
values[groupOffset] = chunks[i : i+32]
|
||||||
|
|
||||||
if groupOffset == 255 || len(chunks)-i <= 32 {
|
if groupOffset == 255 || len(chunks)-i <= 32 {
|
||||||
err = trie.UpdateStem(key[:31], values)
|
err = t.UpdateStem(key[:31], values)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
|
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package trie
|
package bintrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -152,7 +151,7 @@ func TestInsertDuplicateKey(t *testing.T) {
|
||||||
t.Fatal("invalid height")
|
t.Fatal("invalid height")
|
||||||
}
|
}
|
||||||
// Verify that the value is updated
|
// Verify that the value is updated
|
||||||
if !bytes.Equal(tree.(*bintrie.StemNode).Values[1], twoKey[:]) {
|
if !bytes.Equal(tree.(*StemNode).Values[1], twoKey[:]) {
|
||||||
t.Fatal("invalid height")
|
t.Fatal("invalid height")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,7 +405,7 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
|
||||||
// loaded blob will be tracked, while it's not required here since
|
// loaded blob will be tracked, while it's not required here since
|
||||||
// all loaded nodes won't be linked to trie at all and track nodes
|
// all loaded nodes won't be linked to trie at all and track nodes
|
||||||
// may lead to out-of-memory issue.
|
// may lead to out-of-memory issue.
|
||||||
blob, err := it.trie.reader.node(path, common.BytesToHash(hash))
|
blob, err := it.trie.reader.Node(path, common.BytesToHash(hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +426,7 @@ func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error)
|
||||||
// loaded blob will be tracked, while it's not required here since
|
// loaded blob will be tracked, while it's not required here since
|
||||||
// all loaded nodes won't be linked to trie at all and track nodes
|
// all loaded nodes won't be linked to trie at all and track nodes
|
||||||
// may lead to out-of-memory issue.
|
// may lead to out-of-memory issue.
|
||||||
return it.trie.reader.node(path, common.BytesToHash(hash))
|
return it.trie.reader.Node(path, common.BytesToHash(hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
|
func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
||||||
// loaded blob will be tracked, while it's not required here since
|
// loaded blob will be tracked, while it's not required here since
|
||||||
// all loaded nodes won't be linked to trie at all and track nodes
|
// all loaded nodes won't be linked to trie at all and track nodes
|
||||||
// may lead to out-of-memory issue.
|
// may lead to out-of-memory issue.
|
||||||
blob, err := t.reader.node(prefix, common.BytesToHash(n))
|
blob, err := t.reader.Node(prefix, common.BytesToHash(n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unhandled trie error in Trie.Prove", "err", err)
|
log.Error("Unhandled trie error in Trie.Prove", "err", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ type Trie struct {
|
||||||
uncommitted int
|
uncommitted int
|
||||||
|
|
||||||
// reader is the handler trie can retrieve nodes from.
|
// reader is the handler trie can retrieve nodes from.
|
||||||
reader *trieReader
|
reader *TrieReader
|
||||||
|
|
||||||
// Various tracers for capturing the modifications to trie
|
// Various tracers for capturing the modifications to trie
|
||||||
opTracer *opTracer
|
opTracer *opTracer
|
||||||
|
|
@ -88,7 +88,7 @@ func (t *Trie) Copy() *Trie {
|
||||||
// empty, otherwise, the root node must be present in database or returns
|
// empty, otherwise, the root node must be present in database or returns
|
||||||
// a MissingNodeError if not.
|
// a MissingNodeError if not.
|
||||||
func New(id *ID, db database.NodeDatabase) (*Trie, error) {
|
func New(id *ID, db database.NodeDatabase) (*Trie, error) {
|
||||||
reader, err := newTrieReader(id.StateRoot, id.Owner, db)
|
reader, err := NewTrieReader(id.StateRoot, id.Owner, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +289,7 @@ func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnod
|
||||||
if hash == nil {
|
if hash == nil {
|
||||||
return nil, origNode, 0, errors.New("non-consensus node")
|
return nil, origNode, 0, errors.New("non-consensus node")
|
||||||
}
|
}
|
||||||
blob, err := t.reader.node(path, common.BytesToHash(hash))
|
blob, err := t.reader.Node(path, common.BytesToHash(hash))
|
||||||
return blob, origNode, 1, err
|
return blob, origNode, 1, err
|
||||||
}
|
}
|
||||||
// Path still needs to be traversed, descend into children
|
// Path still needs to be traversed, descend into children
|
||||||
|
|
@ -655,7 +655,7 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
|
||||||
// node's original value. The rlp-encoded blob is preferred to be loaded from
|
// node's original value. The rlp-encoded blob is preferred to be loaded from
|
||||||
// database because it's easy to decode node while complex to encode node to blob.
|
// database because it's easy to decode node while complex to encode node to blob.
|
||||||
func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
|
func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
|
||||||
blob, err := t.reader.node(prefix, common.BytesToHash(n))
|
blob, err := t.reader.Node(prefix, common.BytesToHash(n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,30 +22,30 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/triedb/database"
|
"github.com/ethereum/go-ethereum/triedb/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// trieReader is a wrapper of the underlying node reader. It's not safe
|
// TrieReader is a wrapper of the underlying node reader. It's not safe
|
||||||
// for concurrent usage.
|
// for concurrent usage.
|
||||||
type trieReader struct {
|
type TrieReader struct {
|
||||||
owner common.Hash
|
owner common.Hash
|
||||||
reader database.NodeReader
|
reader database.NodeReader
|
||||||
banned map[string]struct{} // Marker to prevent node from being accessed, for tests
|
banned map[string]struct{} // Marker to prevent node from being accessed, for tests
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTrieReader initializes the trie reader with the given node reader.
|
// NewTrieReader initializes the trie reader with the given node reader.
|
||||||
func newTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*trieReader, error) {
|
func NewTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*TrieReader, error) {
|
||||||
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
|
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
|
||||||
return &trieReader{owner: owner}, nil
|
return &TrieReader{owner: owner}, nil
|
||||||
}
|
}
|
||||||
reader, err := db.NodeReader(stateRoot)
|
reader, err := db.NodeReader(stateRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err}
|
return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err}
|
||||||
}
|
}
|
||||||
return &trieReader{owner: owner, reader: reader}, nil
|
return &TrieReader{owner: owner, reader: reader}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newEmptyReader initializes the pure in-memory reader. All read operations
|
// newEmptyReader initializes the pure in-memory reader. All read operations
|
||||||
// should be forbidden and returns the MissingNodeError.
|
// should be forbidden and returns the MissingNodeError.
|
||||||
func newEmptyReader() *trieReader {
|
func newEmptyReader() *TrieReader {
|
||||||
return &trieReader{}
|
return &TrieReader{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// node retrieves the rlp-encoded trie node with the provided trie node
|
// node retrieves the rlp-encoded trie node with the provided trie node
|
||||||
|
|
@ -54,7 +54,7 @@ func newEmptyReader() *trieReader {
|
||||||
//
|
//
|
||||||
// Don't modify the returned byte slice since it's not deep-copied and
|
// Don't modify the returned byte slice since it's not deep-copied and
|
||||||
// still be referenced by database.
|
// still be referenced by database.
|
||||||
func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) {
|
func (r *TrieReader) Node(path []byte, hash common.Hash) ([]byte, error) {
|
||||||
// Perform the logics in tests for preventing trie node access.
|
// Perform the logics in tests for preventing trie node access.
|
||||||
if r.banned != nil {
|
if r.banned != nil {
|
||||||
if _, ok := r.banned[string(path)]; ok {
|
if _, ok := r.banned[string(path)]; ok {
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,13 @@ var (
|
||||||
type VerkleTrie struct {
|
type VerkleTrie struct {
|
||||||
root verkle.VerkleNode
|
root verkle.VerkleNode
|
||||||
cache *utils.PointCache
|
cache *utils.PointCache
|
||||||
reader *trieReader
|
reader *TrieReader
|
||||||
tracer *prevalueTracer
|
tracer *prevalueTracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVerkleTrie constructs a verkle tree based on the specified root hash.
|
// NewVerkleTrie constructs a verkle tree based on the specified root hash.
|
||||||
func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.PointCache) (*VerkleTrie, error) {
|
func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.PointCache) (*VerkleTrie, error) {
|
||||||
reader, err := newTrieReader(root, common.Hash{}, db)
|
reader, err := NewTrieReader(root, common.Hash{}, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +72,10 @@ func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.Poin
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *VerkleTrie) FlatdbNodeResolver(path []byte) ([]byte, error) {
|
||||||
|
return t.reader.Node(path, common.Hash{})
|
||||||
|
}
|
||||||
|
|
||||||
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
||||||
// to store a value.
|
// to store a value.
|
||||||
func (t *VerkleTrie) GetKey(key []byte) []byte {
|
func (t *VerkleTrie) GetKey(key []byte) []byte {
|
||||||
|
|
@ -443,7 +447,7 @@ func (t *VerkleTrie) ToDot() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
|
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
|
||||||
blob, err := t.reader.node(path, common.Hash{})
|
blob, err := t.reader.Node(path, common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue