mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 06:06:44 +00:00
Implement EIP-7864 binary trie with SHA256 hash
- Add binary trie implementation in trie/bintrie package - Implement InternalNode, StemNode, Empty, and HashedNode types - Add comprehensive test coverage for binary trie operations - Switch from blake3 to sha256 hashing for compatibility - Add iterator functionality for binary trie traversal - Update PrevalueTracer interface to support binary operations - Include proper error handling and edge case coverage Co-authored-by: Guillaume Ballet <gballet@gmail.com>
This commit is contained in:
parent
f13dfc5baa
commit
c5db47cc4e
17 changed files with 183 additions and 217 deletions
1
go.mod
1
go.mod
|
|
@ -62,7 +62,6 @@ require (
|
||||||
github.com/supranational/blst v0.3.14
|
github.com/supranational/blst v0.3.14
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||||
github.com/urfave/cli/v2 v2.27.5
|
github.com/urfave/cli/v2 v2.27.5
|
||||||
github.com/zeebo/blake3 v0.2.4
|
|
||||||
go.uber.org/automaxprocs v1.5.2
|
go.uber.org/automaxprocs v1.5.2
|
||||||
go.uber.org/goleak v1.3.0
|
go.uber.org/goleak v1.3.0
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.36.0
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -365,12 +365,6 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
|
||||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
|
||||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
|
||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|
||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
|
||||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ const (
|
||||||
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
|
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nodeTypeStem = iota + 1 // Stem node, contains a stem and a bitmap of values
|
||||||
|
nodeTypeInternal
|
||||||
|
)
|
||||||
|
|
||||||
// BinaryNode is an interface for a binary trie node.
|
// BinaryNode is an interface for a binary trie node.
|
||||||
type BinaryNode interface {
|
type BinaryNode interface {
|
||||||
Get([]byte, NodeResolverFn) ([]byte, error)
|
Get([]byte, NodeResolverFn) ([]byte, error)
|
||||||
|
|
@ -55,13 +60,13 @@ func SerializeNode(node BinaryNode) []byte {
|
||||||
switch n := (node).(type) {
|
switch n := (node).(type) {
|
||||||
case *InternalNode:
|
case *InternalNode:
|
||||||
var serialized [65]byte
|
var serialized [65]byte
|
||||||
serialized[0] = 1
|
serialized[0] = nodeTypeInternal
|
||||||
copy(serialized[1:33], n.Left.Hash().Bytes())
|
copy(serialized[1:33], n.left.Hash().Bytes())
|
||||||
copy(serialized[33:65], n.Right.Hash().Bytes())
|
copy(serialized[33:65], n.right.Hash().Bytes())
|
||||||
return serialized[:]
|
return serialized[:]
|
||||||
case *StemNode:
|
case *StemNode:
|
||||||
var serialized [32 + 256*32]byte
|
var serialized [32 + 256*32]byte
|
||||||
serialized[0] = 2
|
serialized[0] = nodeTypeStem
|
||||||
copy(serialized[1:32], node.(*StemNode).Stem)
|
copy(serialized[1:32], node.(*StemNode).Stem)
|
||||||
bitmap := serialized[32:64]
|
bitmap := serialized[32:64]
|
||||||
offset := 64
|
offset := 64
|
||||||
|
|
@ -78,6 +83,8 @@ func SerializeNode(node BinaryNode) []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var invalidSerializedLength = errors.New("invalid serialized node length")
|
||||||
|
|
||||||
// DeserializeNode deserializes a binary trie node from a byte slice.
|
// DeserializeNode deserializes a binary trie node from a byte slice.
|
||||||
func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
|
func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
|
||||||
if len(serialized) == 0 {
|
if len(serialized) == 0 {
|
||||||
|
|
@ -85,22 +92,28 @@ func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch serialized[0] {
|
switch serialized[0] {
|
||||||
case 1:
|
case nodeTypeInternal:
|
||||||
if len(serialized) != 65 {
|
if len(serialized) != 65 {
|
||||||
return nil, errors.New("invalid serialized node length")
|
return nil, invalidSerializedLength
|
||||||
}
|
}
|
||||||
return &InternalNode{
|
return &InternalNode{
|
||||||
depth: depth,
|
depth: depth,
|
||||||
Left: HashedNode(common.BytesToHash(serialized[1:33])),
|
left: HashedNode(common.BytesToHash(serialized[1:33])),
|
||||||
Right: HashedNode(common.BytesToHash(serialized[33:65])),
|
right: HashedNode(common.BytesToHash(serialized[33:65])),
|
||||||
}, nil
|
}, nil
|
||||||
case 2:
|
case nodeTypeStem:
|
||||||
|
if len(serialized) < 64 {
|
||||||
|
return nil, invalidSerializedLength
|
||||||
|
}
|
||||||
var values [256][]byte
|
var values [256][]byte
|
||||||
bitmap := serialized[32:64]
|
bitmap := serialized[32:64]
|
||||||
offset := 64
|
offset := 64
|
||||||
|
|
||||||
for i := range 256 {
|
for i := range 256 {
|
||||||
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
|
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
|
||||||
|
if len(serialized) < offset+32 {
|
||||||
|
return nil, invalidSerializedLength
|
||||||
|
}
|
||||||
values[i] = serialized[offset : offset+32]
|
values[i] = serialized[offset : offset+32]
|
||||||
offset += 32
|
offset += 32
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package bintrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -32,16 +31,16 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 5,
|
depth: 5,
|
||||||
Left: HashedNode(leftHash),
|
left: HashedNode(leftHash),
|
||||||
Right: HashedNode(rightHash),
|
right: HashedNode(rightHash),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the node
|
// Serialize the node
|
||||||
serialized := SerializeNode(node)
|
serialized := SerializeNode(node)
|
||||||
|
|
||||||
// Check the serialized format
|
// Check the serialized format
|
||||||
if serialized[0] != 1 {
|
if serialized[0] != nodeTypeInternal {
|
||||||
t.Errorf("Expected type byte to be 1, got %d", serialized[0])
|
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(serialized) != 65 {
|
if len(serialized) != 65 {
|
||||||
|
|
@ -66,12 +65,12 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the left and right hashes
|
// Check the left and right hashes
|
||||||
if internalNode.Left.Hash() != leftHash {
|
if internalNode.left.Hash() != leftHash {
|
||||||
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.Left.Hash())
|
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.left.Hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
if internalNode.Right.Hash() != rightHash {
|
if internalNode.right.Hash() != rightHash {
|
||||||
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.Right.Hash())
|
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.right.Hash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,8 +98,8 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
|
||||||
serialized := SerializeNode(node)
|
serialized := SerializeNode(node)
|
||||||
|
|
||||||
// Check the serialized format
|
// Check the serialized format
|
||||||
if serialized[0] != 2 {
|
if serialized[0] != nodeTypeStem {
|
||||||
t.Errorf("Expected type byte to be 2, got %d", serialized[0])
|
t.Errorf("Expected type byte to be %d, got %d", nodeTypeStem, serialized[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the stem is correctly serialized
|
// Check the stem is correctly serialized
|
||||||
|
|
@ -137,8 +136,13 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that other values are nil
|
// Check that other values are nil
|
||||||
if stemNode.Values[1] != nil {
|
for i := range NodeWidth {
|
||||||
t.Errorf("Expected nil value at index 1, got %x", stemNode.Values[1])
|
if i == 0 || i == 10 || i == 255 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stemNode.Values[i] != nil {
|
||||||
|
t.Errorf("Expected nil value at index %d, got %x", i, stemNode.Values[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,7 +174,7 @@ func TestDeserializeInvalidType(t *testing.T) {
|
||||||
// TestDeserializeInvalidLength tests deserialization with invalid data length
|
// TestDeserializeInvalidLength tests deserialization with invalid data length
|
||||||
func TestDeserializeInvalidLength(t *testing.T) {
|
func TestDeserializeInvalidLength(t *testing.T) {
|
||||||
// InternalNode with type byte 1 but wrong length
|
// InternalNode with type byte 1 but wrong length
|
||||||
invalidData := []byte{1, 0, 0} // Too short for internal node
|
invalidData := []byte{nodeTypeInternal, 0, 0} // Too short for internal node
|
||||||
|
|
||||||
_, err := DeserializeNode(invalidData, 0)
|
_, err := DeserializeNode(invalidData, 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -246,24 +250,3 @@ func TestKeyToPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func (h HashedNode) toDot(parent string, path string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error {
|
func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error {
|
||||||
panic("not implemented") // TODO: Implement
|
return errors.New("collectNodes not implemented for hashed node")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HashedNode) GetHeight() int {
|
func (h HashedNode) GetHeight() int {
|
||||||
|
|
|
||||||
|
|
@ -104,52 +104,6 @@ func TestHashedNodeInsertValuesAtStem(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// TestHashedNodeToDot tests the toDot method for visualization
|
||||||
func TestHashedNodeToDot(t *testing.T) {
|
func TestHashedNodeToDot(t *testing.T) {
|
||||||
hash := common.HexToHash("0x1234")
|
hash := common.HexToHash("0x1234")
|
||||||
|
|
@ -170,5 +124,5 @@ func TestHashedNodeToDot(t *testing.T) {
|
||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
func contains(s, substr string) bool {
|
func contains(s, substr string) bool {
|
||||||
return len(s) >= len(substr) && s[:len(s)] != "" && len(substr) > 0
|
return len(s) >= len(substr) && s != "" && len(substr) > 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/zeebo/blake3"
|
"crypto/sha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
func keyToPath(depth int, key []byte) ([]byte, error) {
|
func keyToPath(depth int, key []byte) ([]byte, error) {
|
||||||
|
|
@ -41,7 +41,7 @@ func keyToPath(depth int, key []byte) ([]byte, error) {
|
||||||
|
|
||||||
// InternalNode is a binary trie internal node.
|
// InternalNode is a binary trie internal node.
|
||||||
type InternalNode struct {
|
type InternalNode struct {
|
||||||
Left, Right BinaryNode
|
left, right BinaryNode
|
||||||
depth int
|
depth int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,9 +54,9 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([
|
||||||
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
|
||||||
var child *BinaryNode
|
var child *BinaryNode
|
||||||
if bit == 0 {
|
if bit == 0 {
|
||||||
child = &bt.Left
|
child = &bt.left
|
||||||
} else {
|
} else {
|
||||||
child = &bt.Right
|
child = &bt.right
|
||||||
}
|
}
|
||||||
|
|
||||||
if hn, ok := (*child).(HashedNode); ok {
|
if hn, ok := (*child).(HashedNode); ok {
|
||||||
|
|
@ -96,22 +96,22 @@ func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn
|
||||||
// Copy creates a deep copy of the node.
|
// Copy creates a deep copy of the node.
|
||||||
func (bt *InternalNode) Copy() BinaryNode {
|
func (bt *InternalNode) Copy() BinaryNode {
|
||||||
return &InternalNode{
|
return &InternalNode{
|
||||||
Left: bt.Left.Copy(),
|
left: bt.left.Copy(),
|
||||||
Right: bt.Right.Copy(),
|
right: bt.right.Copy(),
|
||||||
depth: bt.depth,
|
depth: bt.depth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hash of the node.
|
// Hash returns the hash of the node.
|
||||||
func (bt *InternalNode) Hash() common.Hash {
|
func (bt *InternalNode) Hash() common.Hash {
|
||||||
h := blake3.New()
|
h := sha256.New()
|
||||||
if bt.Left != nil {
|
if bt.left != nil {
|
||||||
h.Write(bt.Left.Hash().Bytes())
|
h.Write(bt.left.Hash().Bytes())
|
||||||
} else {
|
} else {
|
||||||
h.Write(zero[:])
|
h.Write(zero[:])
|
||||||
}
|
}
|
||||||
if bt.Right != nil {
|
if bt.right != nil {
|
||||||
h.Write(bt.Right.Hash().Bytes())
|
h.Write(bt.right.Hash().Bytes())
|
||||||
} else {
|
} else {
|
||||||
h.Write(zero[:])
|
h.Write(zero[:])
|
||||||
}
|
}
|
||||||
|
|
@ -127,9 +127,9 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if bit == 0 {
|
if bit == 0 {
|
||||||
child = &bt.Left
|
child = &bt.left
|
||||||
} else {
|
} else {
|
||||||
child = &bt.Right
|
child = &bt.right
|
||||||
}
|
}
|
||||||
|
|
||||||
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
|
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
|
||||||
|
|
@ -139,21 +139,21 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
|
||||||
// CollectNodes collects all child nodes at a given path, and flushes it
|
// CollectNodes collects all child nodes at a given path, and flushes it
|
||||||
// into the provided node collector.
|
// into the provided node collector.
|
||||||
func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error {
|
func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error {
|
||||||
if bt.Left != nil {
|
if bt.left != nil {
|
||||||
var p [256]byte
|
var p [256]byte
|
||||||
copy(p[:], path)
|
copy(p[:], path)
|
||||||
childpath := p[:len(path)]
|
childpath := p[:len(path)]
|
||||||
childpath = append(childpath, 0)
|
childpath = append(childpath, 0)
|
||||||
if err := bt.Left.CollectNodes(childpath, flushfn); err != nil {
|
if err := bt.left.CollectNodes(childpath, flushfn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if bt.Right != nil {
|
if bt.right != nil {
|
||||||
var p [256]byte
|
var p [256]byte
|
||||||
copy(p[:], path)
|
copy(p[:], path)
|
||||||
childpath := p[:len(path)]
|
childpath := p[:len(path)]
|
||||||
childpath = append(childpath, 1)
|
childpath = append(childpath, 1)
|
||||||
if err := bt.Right.CollectNodes(childpath, flushfn); err != nil {
|
if err := bt.right.CollectNodes(childpath, flushfn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -167,11 +167,11 @@ func (bt *InternalNode) GetHeight() int {
|
||||||
leftHeight int
|
leftHeight int
|
||||||
rightHeight int
|
rightHeight int
|
||||||
)
|
)
|
||||||
if bt.Left != nil {
|
if bt.left != nil {
|
||||||
leftHeight = bt.Left.GetHeight()
|
leftHeight = bt.left.GetHeight()
|
||||||
}
|
}
|
||||||
if bt.Right != nil {
|
if bt.right != nil {
|
||||||
rightHeight = bt.Right.GetHeight()
|
rightHeight = bt.right.GetHeight()
|
||||||
}
|
}
|
||||||
return 1 + max(leftHeight, rightHeight)
|
return 1 + max(leftHeight, rightHeight)
|
||||||
}
|
}
|
||||||
|
|
@ -183,11 +183,11 @@ func (bt *InternalNode) toDot(parent, path string) string {
|
||||||
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
|
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bt.Left != nil {
|
if bt.left != nil {
|
||||||
ret = fmt.Sprintf("%s%s", ret, bt.Left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
|
ret = fmt.Sprintf("%s%s", ret, bt.left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
|
||||||
}
|
}
|
||||||
if bt.Right != nil {
|
if bt.right != nil {
|
||||||
ret = fmt.Sprintf("%s%s", ret, bt.Right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
|
ret = fmt.Sprintf("%s%s", ret, bt.right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ func TestInternalNodeGet(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: &StemNode{
|
left: &StemNode{
|
||||||
Stem: leftStem,
|
Stem: leftStem,
|
||||||
Values: leftValues[:],
|
Values: leftValues[:],
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
Right: &StemNode{
|
right: &StemNode{
|
||||||
Stem: rightStem,
|
Stem: rightStem,
|
||||||
Values: rightValues[:],
|
Values: rightValues[:],
|
||||||
depth: 1,
|
depth: 1,
|
||||||
|
|
@ -80,8 +80,8 @@ func TestInternalNodeGetWithResolver(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: hashedChild,
|
left: hashedChild,
|
||||||
Right: Empty{},
|
right: Empty{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock resolver that returns a stem node
|
// Mock resolver that returns a stem node
|
||||||
|
|
@ -119,8 +119,8 @@ func TestInternalNodeInsert(t *testing.T) {
|
||||||
// Start with an internal node with empty children
|
// Start with an internal node with empty children
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: Empty{},
|
left: Empty{},
|
||||||
Right: Empty{},
|
right: Empty{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a value into the left subtree
|
// Insert a value into the left subtree
|
||||||
|
|
@ -139,9 +139,9 @@ func TestInternalNodeInsert(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that left child is now a StemNode
|
// Check that left child is now a StemNode
|
||||||
leftStem, ok := internalNode.Left.(*StemNode)
|
leftStem, ok := internalNode.left.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
|
t.Fatalf("Expected left child to be StemNode, got %T", internalNode.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the inserted value
|
// Check the inserted value
|
||||||
|
|
@ -150,9 +150,9 @@ func TestInternalNodeInsert(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right child should still be Empty
|
// Right child should still be Empty
|
||||||
_, ok = internalNode.Right.(Empty)
|
_, ok = internalNode.right.(Empty)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("Expected right child to remain Empty, got %T", internalNode.Right)
|
t.Errorf("Expected right child to remain Empty, got %T", internalNode.right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,8 +176,8 @@ func TestInternalNodeCopy(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: leftStem,
|
left: leftStem,
|
||||||
Right: rightStem,
|
right: rightStem,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a copy
|
// Create a copy
|
||||||
|
|
@ -193,14 +193,14 @@ func TestInternalNodeCopy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that children are copied
|
// Check that children are copied
|
||||||
copiedLeft, ok := copiedInternal.Left.(*StemNode)
|
copiedLeft, ok := copiedInternal.left.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected left child to be StemNode, got %T", copiedInternal.Left)
|
t.Fatalf("Expected left child to be StemNode, got %T", copiedInternal.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
copiedRight, ok := copiedInternal.Right.(*StemNode)
|
copiedRight, ok := copiedInternal.right.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected right child to be StemNode, got %T", copiedInternal.Right)
|
t.Fatalf("Expected right child to be StemNode, got %T", copiedInternal.right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify deep copy (children should be different objects)
|
// Verify deep copy (children should be different objects)
|
||||||
|
|
@ -225,8 +225,8 @@ func TestInternalNodeHash(t *testing.T) {
|
||||||
// Create an internal node
|
// Create an internal node
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: HashedNode(common.HexToHash("0x1111")),
|
left: HashedNode(common.HexToHash("0x1111")),
|
||||||
Right: HashedNode(common.HexToHash("0x2222")),
|
right: HashedNode(common.HexToHash("0x2222")),
|
||||||
}
|
}
|
||||||
|
|
||||||
hash1 := node.Hash()
|
hash1 := node.Hash()
|
||||||
|
|
@ -238,7 +238,7 @@ func TestInternalNodeHash(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changing a child should change the hash
|
// Changing a child should change the hash
|
||||||
node.Left = HashedNode(common.HexToHash("0x3333"))
|
node.left = HashedNode(common.HexToHash("0x3333"))
|
||||||
hash3 := node.Hash()
|
hash3 := node.Hash()
|
||||||
if hash1 == hash3 {
|
if hash1 == hash3 {
|
||||||
t.Error("Hash didn't change after modifying left child")
|
t.Error("Hash didn't change after modifying left child")
|
||||||
|
|
@ -247,8 +247,8 @@ func TestInternalNodeHash(t *testing.T) {
|
||||||
// Test with nil children (should use zero hash)
|
// Test with nil children (should use zero hash)
|
||||||
nodeWithNil := &InternalNode{
|
nodeWithNil := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: nil,
|
left: nil,
|
||||||
Right: HashedNode(common.HexToHash("0x4444")),
|
right: HashedNode(common.HexToHash("0x4444")),
|
||||||
}
|
}
|
||||||
hashWithNil := nodeWithNil.Hash()
|
hashWithNil := nodeWithNil.Hash()
|
||||||
if hashWithNil == (common.Hash{}) {
|
if hashWithNil == (common.Hash{}) {
|
||||||
|
|
@ -271,12 +271,12 @@ func TestInternalNodeGetValuesAtStem(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: &StemNode{
|
left: &StemNode{
|
||||||
Stem: leftStem,
|
Stem: leftStem,
|
||||||
Values: leftValues[:],
|
Values: leftValues[:],
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
Right: &StemNode{
|
right: &StemNode{
|
||||||
Stem: rightStem,
|
Stem: rightStem,
|
||||||
Values: rightValues[:],
|
Values: rightValues[:],
|
||||||
depth: 1,
|
depth: 1,
|
||||||
|
|
@ -313,8 +313,8 @@ func TestInternalNodeInsertValuesAtStem(t *testing.T) {
|
||||||
// Start with an internal node with empty children
|
// Start with an internal node with empty children
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: Empty{},
|
left: Empty{},
|
||||||
Right: Empty{},
|
right: Empty{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert values at a stem in the left subtree
|
// Insert values at a stem in the left subtree
|
||||||
|
|
@ -334,9 +334,9 @@ func TestInternalNodeInsertValuesAtStem(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that left child is now a StemNode with the values
|
// Check that left child is now a StemNode with the values
|
||||||
leftStem, ok := internalNode.Left.(*StemNode)
|
leftStem, ok := internalNode.left.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
|
t.Fatalf("Expected left child to be StemNode, got %T", internalNode.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(leftStem.Values[5], values[5]) {
|
if !bytes.Equal(leftStem.Values[5], values[5]) {
|
||||||
|
|
@ -365,8 +365,8 @@ func TestInternalNodeCollectNodes(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: leftStem,
|
left: leftStem,
|
||||||
Right: rightStem,
|
right: rightStem,
|
||||||
}
|
}
|
||||||
|
|
||||||
var collectedPaths [][]byte
|
var collectedPaths [][]byte
|
||||||
|
|
@ -410,12 +410,12 @@ func TestInternalNodeGetHeight(t *testing.T) {
|
||||||
// Right subtree: depth 1 (stem)
|
// Right subtree: depth 1 (stem)
|
||||||
leftInternal := &InternalNode{
|
leftInternal := &InternalNode{
|
||||||
depth: 1,
|
depth: 1,
|
||||||
Left: &StemNode{
|
left: &StemNode{
|
||||||
Stem: make([]byte, 31),
|
Stem: make([]byte, 31),
|
||||||
Values: make([][]byte, 256),
|
Values: make([][]byte, 256),
|
||||||
depth: 2,
|
depth: 2,
|
||||||
},
|
},
|
||||||
Right: Empty{},
|
right: Empty{},
|
||||||
}
|
}
|
||||||
|
|
||||||
rightStem := &StemNode{
|
rightStem := &StemNode{
|
||||||
|
|
@ -426,8 +426,8 @@ func TestInternalNodeGetHeight(t *testing.T) {
|
||||||
|
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 0,
|
depth: 0,
|
||||||
Left: leftInternal,
|
left: leftInternal,
|
||||||
Right: rightStem,
|
right: rightStem,
|
||||||
}
|
}
|
||||||
|
|
||||||
height := node.GetHeight()
|
height := node.GetHeight()
|
||||||
|
|
@ -443,8 +443,8 @@ func TestInternalNodeDepthTooLarge(t *testing.T) {
|
||||||
// Create an internal node at max depth
|
// Create an internal node at max depth
|
||||||
node := &InternalNode{
|
node := &InternalNode{
|
||||||
depth: 31*8 + 1,
|
depth: 31*8 + 1,
|
||||||
Left: Empty{},
|
left: Empty{},
|
||||||
Right: Empty{},
|
right: Empty{},
|
||||||
}
|
}
|
||||||
|
|
||||||
stem := make([]byte, 31)
|
stem := make([]byte, 31)
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
|
|
||||||
// recurse into both children
|
// recurse into both children
|
||||||
if context.Index == 0 {
|
if context.Index == 0 {
|
||||||
if _, isempty := node.Left.(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,9 +79,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.Index == 1 {
|
if context.Index == 1 {
|
||||||
if _, isempty := node.Right.(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,9 +127,9 @@ 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.(*InternalNode).Left = it.current
|
parent.Node.(*InternalNode).left = it.current
|
||||||
} else {
|
} else {
|
||||||
parent.Node.(*InternalNode).Right = it.current
|
parent.Node.(*InternalNode).right = it.current
|
||||||
}
|
}
|
||||||
return it.Next(descend)
|
return it.Next(descend)
|
||||||
case Empty:
|
case Empty:
|
||||||
|
|
@ -177,8 +177,9 @@ func (it *binaryNodeIterator) Path() []byte {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeBlob returns the serialized bytes of the current node.
|
||||||
func (it *binaryNodeIterator) NodeBlob() []byte {
|
func (it *binaryNodeIterator) NodeBlob() []byte {
|
||||||
panic("not completely implemented")
|
return SerializeNode(it.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leaf returns true iff the current node is a leaf node.
|
// Leaf returns true iff the current node is a leaf node.
|
||||||
|
|
@ -215,13 +216,35 @@ 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.(*StemNode)
|
sn, 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// return it.trie.Prove(leaf.Key())
|
proof := make([][]byte, 0, len(it.stack)+NodeWidth)
|
||||||
panic("not completely implemented")
|
|
||||||
|
// Build proof by walking up the stack and collecting sibling hashes
|
||||||
|
for i := range it.stack[:len(it.stack)-2] {
|
||||||
|
state := it.stack[i]
|
||||||
|
internalNode := state.Node.(*InternalNode) // should panic if the node isn't an InternalNode
|
||||||
|
|
||||||
|
// Add the sibling hash to the proof
|
||||||
|
if state.Index == 0 {
|
||||||
|
// We came from left, so include right sibling
|
||||||
|
proof = append(proof, internalNode.right.Hash().Bytes())
|
||||||
|
} else {
|
||||||
|
// We came from right, so include left sibling
|
||||||
|
proof = append(proof, internalNode.left.Hash().Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the stem and siblings
|
||||||
|
proof = append(proof, sn.Stem)
|
||||||
|
for _, v := range sn.Values {
|
||||||
|
proof = append(proof, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return proof
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddResolver sets an intermediate database to use for looking up trie nodes
|
// AddResolver sets an intermediate database to use for looking up trie nodes
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/zeebo/blake3"
|
"crypto/sha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StemNode represents a group of `NodeWith` values sharing the same stem.
|
// StemNode represents a group of `NodeWith` values sharing the same stem.
|
||||||
|
|
@ -47,13 +47,13 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn) (BinaryNo
|
||||||
bt.depth++
|
bt.depth++
|
||||||
var child, other *BinaryNode
|
var child, other *BinaryNode
|
||||||
if bitStem == 0 {
|
if bitStem == 0 {
|
||||||
new.Left = bt
|
new.left = bt
|
||||||
child = &new.Left
|
child = &new.left
|
||||||
other = &new.Right
|
other = &new.right
|
||||||
} else {
|
} else {
|
||||||
new.Right = bt
|
new.right = bt
|
||||||
child = &new.Right
|
child = &new.right
|
||||||
other = &new.Left
|
other = &new.left
|
||||||
}
|
}
|
||||||
|
|
||||||
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
|
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
|
||||||
|
|
@ -107,12 +107,12 @@ func (bt *StemNode) Hash() common.Hash {
|
||||||
var data [NodeWidth]common.Hash
|
var data [NodeWidth]common.Hash
|
||||||
for i, v := range bt.Values {
|
for i, v := range bt.Values {
|
||||||
if v != nil {
|
if v != nil {
|
||||||
h := blake3.Sum256(v)
|
h := sha256.Sum256(v)
|
||||||
data[i] = common.BytesToHash(h[:])
|
data[i] = common.BytesToHash(h[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h := blake3.New()
|
h := sha256.New()
|
||||||
for level := 1; level <= 8; level++ {
|
for level := 1; level <= 8; level++ {
|
||||||
for i := range NodeWidth / (1 << level) {
|
for i := range NodeWidth / (1 << level) {
|
||||||
h.Reset()
|
h.Reset()
|
||||||
|
|
@ -157,13 +157,13 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv
|
||||||
bt.depth++
|
bt.depth++
|
||||||
var child, other *BinaryNode
|
var child, other *BinaryNode
|
||||||
if bitStem == 0 {
|
if bitStem == 0 {
|
||||||
new.Left = bt
|
new.left = bt
|
||||||
child = &new.Left
|
child = &new.left
|
||||||
other = &new.Right
|
other = &new.right
|
||||||
} else {
|
} else {
|
||||||
new.Right = bt
|
new.right = bt
|
||||||
child = &new.Right
|
child = &new.right
|
||||||
other = &new.Left
|
other = &new.left
|
||||||
}
|
}
|
||||||
|
|
||||||
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
|
bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1
|
||||||
|
|
|
||||||
|
|
@ -103,18 +103,18 @@ func TestStemNodeInsertDifferentStem(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original stem should be on the left (bit 0)
|
// Original stem should be on the left (bit 0)
|
||||||
leftStem, ok := internalNode.Left.(*StemNode)
|
leftStem, ok := internalNode.left.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected left child to be StemNode, got %T", internalNode.Left)
|
t.Fatalf("Expected left child to be StemNode, got %T", internalNode.left)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(leftStem.Stem, stem1) {
|
if !bytes.Equal(leftStem.Stem, stem1) {
|
||||||
t.Errorf("Left stem mismatch")
|
t.Errorf("Left stem mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
// New stem should be on the right (bit 1)
|
// New stem should be on the right (bit 1)
|
||||||
rightStem, ok := internalNode.Right.(*StemNode)
|
rightStem, ok := internalNode.right.(*StemNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expected right child to be StemNode, got %T", internalNode.Right)
|
t.Fatalf("Expected right child to be StemNode, got %T", internalNode.right)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(rightStem.Stem, key[:31]) {
|
if !bytes.Equal(rightStem.Stem, key[:31]) {
|
||||||
t.Errorf("Right stem mismatch")
|
t.Errorf("Right stem mismatch")
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ func NewBinaryNode() BinaryNode {
|
||||||
type BinaryTrie struct {
|
type BinaryTrie struct {
|
||||||
root BinaryNode
|
root BinaryNode
|
||||||
reader *trie.TrieReader
|
reader *trie.TrieReader
|
||||||
|
tracer *trie.PrevalueTracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
@ -246,7 +247,7 @@ func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
|
||||||
|
|
||||||
err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
|
err := root.CollectNodes(nil, func(path []byte, node BinaryNode) {
|
||||||
serialized := SerializeNode(node)
|
serialized := SerializeNode(node)
|
||||||
nodeset.AddNode(path, trienode.New(common.Hash{}, serialized))
|
nodeset.AddNode(path, trienode.NewNodeWithPrev(common.Hash{}, serialized, t.tracer.Get(path)))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("CollectNodes failed: %v", err))
|
panic(fmt.Errorf("CollectNodes failed: %v", err))
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func TestSingleEntry(t *testing.T) {
|
||||||
if tree.GetHeight() != 1 {
|
if tree.GetHeight() != 1 {
|
||||||
t.Fatal("invalid depth")
|
t.Fatal("invalid depth")
|
||||||
}
|
}
|
||||||
expected := common.HexToHash("694545468677064fd833cddc8455762fe6b21c6cabe2fc172529e0f573181cd5")
|
expected := common.HexToHash("aab1060e04cb4f5dc6f697ae93156a95714debbf77d54238766adc5709282b6f")
|
||||||
got := tree.Hash()
|
got := tree.Hash()
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Fatalf("invalid tree root, got %x, want %x", got, expected)
|
t.Fatalf("invalid tree root, got %x, want %x", got, expected)
|
||||||
|
|
@ -63,7 +63,7 @@ func TestTwoEntriesDiffFirstBit(t *testing.T) {
|
||||||
if tree.GetHeight() != 2 {
|
if tree.GetHeight() != 2 {
|
||||||
t.Fatal("invalid height")
|
t.Fatal("invalid height")
|
||||||
}
|
}
|
||||||
if tree.Hash() != common.HexToHash("85fc622076752a6fcda2c886c18058d639066a83473d9684704b5a29455ed2ed") {
|
if tree.Hash() != common.HexToHash("dfc69c94013a8b3c65395625a719a87534a7cfd38719251ad8c8ea7fe79f065e") {
|
||||||
t.Fatal("invalid tree root")
|
t.Fatal("invalid tree root")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +190,7 @@ func TestMerkleizeMultipleEntries(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
got := tree.Hash()
|
got := tree.Hash()
|
||||||
expected := common.HexToHash("8c74de28e6bb6b2296cae37cff16266e2dbf533bc204fa4cb0c237761ae8a2c8")
|
expected := common.HexToHash("9317155862f7a3867660ddd0966ff799a3d16aa4df1e70a7516eaa4a675191b5")
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Fatalf("invalid root, expected=%x, got = %x", expected, got)
|
t.Fatalf("invalid root, expected=%x, got = %x", expected, got)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@ import (
|
||||||
// insertion order.
|
// insertion order.
|
||||||
type committer struct {
|
type committer struct {
|
||||||
nodes *trienode.NodeSet
|
nodes *trienode.NodeSet
|
||||||
tracer *prevalueTracer
|
tracer *PrevalueTracer
|
||||||
collectLeaf bool
|
collectLeaf bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCommitter creates a new committer or picks one from the pool.
|
// newCommitter creates a new committer or picks one from the pool.
|
||||||
func newCommitter(nodeset *trienode.NodeSet, tracer *prevalueTracer, collectLeaf bool) *committer {
|
func newCommitter(nodeset *trienode.NodeSet, tracer *PrevalueTracer, collectLeaf bool) *committer {
|
||||||
return &committer{
|
return &committer{
|
||||||
nodes: nodeset,
|
nodes: nodeset,
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
|
|
@ -142,7 +142,7 @@ func (c *committer) store(path []byte, n node) node {
|
||||||
// The node is embedded in its parent, in other words, this node
|
// The node is embedded in its parent, in other words, this node
|
||||||
// will not be stored in the database independently, mark it as
|
// will not be stored in the database independently, mark it as
|
||||||
// deleted only if the node was existent in database before.
|
// deleted only if the node was existent in database before.
|
||||||
origin := c.tracer.get(path)
|
origin := c.tracer.Get(path)
|
||||||
if len(origin) != 0 {
|
if len(origin) != 0 {
|
||||||
c.nodes.AddNode(path, trienode.NewDeletedWithPrev(origin))
|
c.nodes.AddNode(path, trienode.NewDeletedWithPrev(origin))
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ func (c *committer) store(path []byte, n node) node {
|
||||||
}
|
}
|
||||||
// Collect the dirty node to nodeset for return.
|
// Collect the dirty node to nodeset for return.
|
||||||
nhash := common.BytesToHash(hash)
|
nhash := common.BytesToHash(hash)
|
||||||
c.nodes.AddNode(path, trienode.NewNodeWithPrev(nhash, nodeToBytes(n), c.tracer.get(path)))
|
c.nodes.AddNode(path, trienode.NewNodeWithPrev(nhash, nodeToBytes(n), c.tracer.Get(path)))
|
||||||
|
|
||||||
// Collect the corresponding leaf node if it's required. We don't check
|
// Collect the corresponding leaf node if it's required. We don't check
|
||||||
// full node since it's impossible to store value in fullNode. The key
|
// full node since it's impossible to store value in fullNode. The key
|
||||||
|
|
|
||||||
|
|
@ -94,20 +94,19 @@ func (t *opTracer) deletedList() [][]byte {
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevalueTracer tracks the original values of resolved trie nodes. Cached trie
|
// PrevalueTracer tracks the original values of resolved trie nodes. Cached trie
|
||||||
// node values are expected to be immutable. A zero-size node value is treated as
|
// node values are expected to be immutable. A zero-size node value is treated as
|
||||||
// non-existent and should not occur in practice.
|
// non-existent and should not occur in practice.
|
||||||
//
|
//
|
||||||
// Note prevalueTracer is not thread-safe, callers should be responsible for
|
// Note PrevalueTracer is thread-safe.
|
||||||
// handling the concurrency issues by themselves.
|
type PrevalueTracer struct {
|
||||||
type prevalueTracer struct {
|
|
||||||
data map[string][]byte
|
data map[string][]byte
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
|
// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
|
||||||
func newPrevalueTracer() *prevalueTracer {
|
func newPrevalueTracer() *PrevalueTracer {
|
||||||
return &prevalueTracer{
|
return &PrevalueTracer{
|
||||||
data: make(map[string][]byte),
|
data: make(map[string][]byte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -115,16 +114,16 @@ func newPrevalueTracer() *prevalueTracer {
|
||||||
// put tracks the newly loaded trie node and caches its RLP-encoded
|
// put tracks the newly loaded trie node and caches its RLP-encoded
|
||||||
// blob internally. Do not modify the value outside this function,
|
// blob internally. Do not modify the value outside this function,
|
||||||
// as it is not deep-copied.
|
// as it is not deep-copied.
|
||||||
func (t *prevalueTracer) put(path []byte, val []byte) {
|
func (t *PrevalueTracer) put(path []byte, val []byte) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
t.data[string(path)] = val
|
t.data[string(path)] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns the cached trie node value. If the node is not found, nil will
|
// Get returns the cached trie node value. If the node is not found, nil will
|
||||||
// be returned.
|
// be returned.
|
||||||
func (t *prevalueTracer) get(path []byte) []byte {
|
func (t *PrevalueTracer) Get(path []byte) []byte {
|
||||||
t.lock.RLock()
|
t.lock.RLock()
|
||||||
defer t.lock.RUnlock()
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
|
|
@ -133,7 +132,7 @@ func (t *prevalueTracer) get(path []byte) []byte {
|
||||||
|
|
||||||
// hasList returns a list of flags indicating whether the corresponding trie nodes
|
// hasList returns a list of flags indicating whether the corresponding trie nodes
|
||||||
// specified by the path exist in the trie.
|
// specified by the path exist in the trie.
|
||||||
func (t *prevalueTracer) hasList(list [][]byte) []bool {
|
func (t *PrevalueTracer) hasList(list [][]byte) []bool {
|
||||||
t.lock.RLock()
|
t.lock.RLock()
|
||||||
defer t.lock.RUnlock()
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
|
|
@ -146,7 +145,7 @@ func (t *prevalueTracer) hasList(list [][]byte) []bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// values returns a list of values of the cached trie nodes.
|
// values returns a list of values of the cached trie nodes.
|
||||||
func (t *prevalueTracer) values() map[string][]byte {
|
func (t *PrevalueTracer) values() map[string][]byte {
|
||||||
t.lock.RLock()
|
t.lock.RLock()
|
||||||
defer t.lock.RUnlock()
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
|
|
@ -154,7 +153,7 @@ func (t *prevalueTracer) values() map[string][]byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset resets the cached content in the prevalueTracer.
|
// reset resets the cached content in the prevalueTracer.
|
||||||
func (t *prevalueTracer) reset() {
|
func (t *PrevalueTracer) reset() {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -162,12 +161,12 @@ func (t *prevalueTracer) reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy returns a copied prevalueTracer instance.
|
// copy returns a copied prevalueTracer instance.
|
||||||
func (t *prevalueTracer) copy() *prevalueTracer {
|
func (t *PrevalueTracer) copy() *PrevalueTracer {
|
||||||
t.lock.RLock()
|
t.lock.RLock()
|
||||||
defer t.lock.RUnlock()
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
// Shadow clone is used, as the cached trie node values are immutable
|
// Shadow clone is used, as the cached trie node values are immutable
|
||||||
return &prevalueTracer{
|
return &PrevalueTracer{
|
||||||
data: maps.Clone(t.data),
|
data: maps.Clone(t.data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ type Trie struct {
|
||||||
|
|
||||||
// Various tracers for capturing the modifications to trie
|
// Various tracers for capturing the modifications to trie
|
||||||
opTracer *opTracer
|
opTracer *opTracer
|
||||||
prevalueTracer *prevalueTracer
|
prevalueTracer *PrevalueTracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFlag returns the cache flag value for a newly created node.
|
// newFlag returns the cache flag value for a newly created node.
|
||||||
|
|
@ -711,7 +711,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||||
}
|
}
|
||||||
nodes := trienode.NewNodeSet(t.owner)
|
nodes := trienode.NewNodeSet(t.owner)
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.get(path)))
|
nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.Get(path)))
|
||||||
}
|
}
|
||||||
return types.EmptyRootHash, nodes // case (b)
|
return types.EmptyRootHash, nodes // case (b)
|
||||||
}
|
}
|
||||||
|
|
@ -729,7 +729,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||||
}
|
}
|
||||||
nodes := trienode.NewNodeSet(t.owner)
|
nodes := trienode.NewNodeSet(t.owner)
|
||||||
for _, path := range t.deletedNodes() {
|
for _, path := range t.deletedNodes() {
|
||||||
nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.get(path)))
|
nodes.AddNode(path, trienode.NewDeletedWithPrev(t.prevalueTracer.Get(path)))
|
||||||
}
|
}
|
||||||
// If the number of changes is below 100, we let one thread handle it
|
// If the number of changes is below 100, we let one thread handle it
|
||||||
t.root = newCommitter(nodes, t.prevalueTracer, collectLeaf).Commit(t.root, t.uncommitted > 100)
|
t.root = newCommitter(nodes, t.prevalueTracer, collectLeaf).Commit(t.root, t.uncommitted > 100)
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ 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.
|
||||||
|
|
@ -293,7 +293,7 @@ func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
||||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
// Hash parameter is not used in pathdb
|
// Hash parameter is not used in pathdb
|
||||||
nodeset.AddNode(node.Path, trienode.NewNodeWithPrev(common.Hash{}, node.SerializedBytes, t.tracer.get(node.Path)))
|
nodeset.AddNode(node.Path, trienode.NewNodeWithPrev(common.Hash{}, node.SerializedBytes, t.tracer.Get(node.Path)))
|
||||||
}
|
}
|
||||||
// Serialize root commitment form
|
// Serialize root commitment form
|
||||||
return t.Hash(), nodeset
|
return t.Hash(), nodeset
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue