diff --git a/go.mod b/go.mod index ffde28f005..2da5b57209 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,6 @@ require ( github.com/supranational/blst v0.3.14 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 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/goleak v1.3.0 golang.org/x/crypto v0.36.0 diff --git a/go.sum b/go.sum index 4a3aac1c12..7cb6fb677a 100644 --- a/go.sum +++ b/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go index b97adeb21e..50d8c0b3ca 100644 --- a/trie/bintrie/binary_node.go +++ b/trie/bintrie/binary_node.go @@ -35,6 +35,11 @@ const ( 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. type BinaryNode interface { Get([]byte, NodeResolverFn) ([]byte, error) @@ -55,13 +60,13 @@ func SerializeNode(node BinaryNode) []byte { switch n := (node).(type) { case *InternalNode: var serialized [65]byte - serialized[0] = 1 - copy(serialized[1:33], n.Left.Hash().Bytes()) - copy(serialized[33:65], n.Right.Hash().Bytes()) + serialized[0] = nodeTypeInternal + copy(serialized[1:33], n.left.Hash().Bytes()) + copy(serialized[33:65], n.right.Hash().Bytes()) return serialized[:] case *StemNode: var serialized [32 + 256*32]byte - serialized[0] = 2 + serialized[0] = nodeTypeStem copy(serialized[1:32], node.(*StemNode).Stem) bitmap := serialized[32: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. func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) { if len(serialized) == 0 { @@ -85,22 +92,28 @@ func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) { } switch serialized[0] { - case 1: + case nodeTypeInternal: if len(serialized) != 65 { - return nil, errors.New("invalid serialized node length") + return nil, invalidSerializedLength } return &InternalNode{ depth: depth, - Left: HashedNode(common.BytesToHash(serialized[1:33])), - Right: HashedNode(common.BytesToHash(serialized[33:65])), + left: HashedNode(common.BytesToHash(serialized[1:33])), + right: HashedNode(common.BytesToHash(serialized[33:65])), }, nil - case 2: + case nodeTypeStem: + if len(serialized) < 64 { + return nil, invalidSerializedLength + } var values [256][]byte bitmap := serialized[32:64] offset := 64 for i := range 256 { if bitmap[i/8]>>(7-(i%8))&1 == 1 { + if len(serialized) < offset+32 { + return nil, invalidSerializedLength + } values[i] = serialized[offset : offset+32] offset += 32 } diff --git a/trie/bintrie/binary_node_test.go b/trie/bintrie/binary_node_test.go index 5db6e111f0..b21daaab69 100644 --- a/trie/bintrie/binary_node_test.go +++ b/trie/bintrie/binary_node_test.go @@ -18,7 +18,6 @@ package bintrie import ( "bytes" - "errors" "testing" "github.com/ethereum/go-ethereum/common" @@ -32,16 +31,16 @@ func TestSerializeDeserializeInternalNode(t *testing.T) { node := &InternalNode{ depth: 5, - Left: HashedNode(leftHash), - Right: HashedNode(rightHash), + 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 serialized[0] != nodeTypeInternal { + t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0]) } if len(serialized) != 65 { @@ -66,12 +65,12 @@ func TestSerializeDeserializeInternalNode(t *testing.T) { } // 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.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()) + if internalNode.right.Hash() != rightHash { + 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) // Check the serialized format - if serialized[0] != 2 { - t.Errorf("Expected type byte to be 2, got %d", serialized[0]) + if serialized[0] != nodeTypeStem { + t.Errorf("Expected type byte to be %d, got %d", nodeTypeStem, serialized[0]) } // Check the stem is correctly serialized @@ -137,8 +136,13 @@ func TestSerializeDeserializeStemNode(t *testing.T) { } // Check that other values are nil - if stemNode.Values[1] != nil { - t.Errorf("Expected nil value at index 1, got %x", stemNode.Values[1]) + for i := range NodeWidth { + 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 func TestDeserializeInvalidLength(t *testing.T) { // 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) 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 -} diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go index e5ee79a460..05e50db9fb 100644 --- a/trie/bintrie/hashed_node.go +++ b/trie/bintrie/hashed_node.go @@ -58,7 +58,7 @@ func (h HashedNode) toDot(parent string, path string) string { } 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 { diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go index dbc6a7a244..78f8ad72af 100644 --- a/trie/bintrie/hashed_node_test.go +++ b/trie/bintrie/hashed_node_test.go @@ -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 func TestHashedNodeToDot(t *testing.T) { hash := common.HexToHash("0x1234") @@ -170,5 +124,5 @@ func TestHashedNodeToDot(t *testing.T) { // Helper function 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 } diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go index 6287606c50..af9fce5ef9 100644 --- a/trie/bintrie/internal_node.go +++ b/trie/bintrie/internal_node.go @@ -21,7 +21,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/zeebo/blake3" + "crypto/sha256" ) 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. type InternalNode struct { - Left, Right BinaryNode + left, right BinaryNode depth int } @@ -54,9 +54,9 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([ bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1 var child *BinaryNode if bit == 0 { - child = &bt.Left + child = &bt.left } else { - child = &bt.Right + child = &bt.right } 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. func (bt *InternalNode) Copy() BinaryNode { return &InternalNode{ - Left: bt.Left.Copy(), - Right: bt.Right.Copy(), + left: bt.left.Copy(), + right: bt.right.Copy(), depth: bt.depth, } } // Hash returns the hash of the node. func (bt *InternalNode) Hash() common.Hash { - h := blake3.New() - if bt.Left != nil { - h.Write(bt.Left.Hash().Bytes()) + h := sha256.New() + if bt.left != nil { + h.Write(bt.left.Hash().Bytes()) } else { h.Write(zero[:]) } - if bt.Right != nil { - h.Write(bt.Right.Hash().Bytes()) + if bt.right != nil { + h.Write(bt.right.Hash().Bytes()) } else { h.Write(zero[:]) } @@ -127,9 +127,9 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve err error ) if bit == 0 { - child = &bt.Left + child = &bt.left } else { - child = &bt.Right + child = &bt.right } *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 // into the provided node collector. func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error { - if bt.Left != nil { + if bt.left != nil { var p [256]byte copy(p[:], path) childpath := p[:len(path)] childpath = append(childpath, 0) - if err := bt.Left.CollectNodes(childpath, flushfn); err != nil { + if err := bt.left.CollectNodes(childpath, flushfn); err != nil { return err } } - if bt.Right != nil { + if bt.right != nil { var p [256]byte copy(p[:], path) childpath := p[:len(path)] childpath = append(childpath, 1) - if err := bt.Right.CollectNodes(childpath, flushfn); err != nil { + if err := bt.right.CollectNodes(childpath, flushfn); err != nil { return err } } @@ -167,11 +167,11 @@ func (bt *InternalNode) GetHeight() int { leftHeight int rightHeight int ) - if bt.Left != nil { - leftHeight = bt.Left.GetHeight() + if bt.left != nil { + leftHeight = bt.left.GetHeight() } - if bt.Right != nil { - rightHeight = bt.Right.GetHeight() + if bt.right != nil { + rightHeight = bt.right.GetHeight() } 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) } - if bt.Left != nil { - ret = fmt.Sprintf("%s%s", ret, bt.Left.toDot(me, fmt.Sprintf("%s%02x", path, 0))) + if bt.left != nil { + ret = fmt.Sprintf("%s%s", ret, bt.left.toDot(me, fmt.Sprintf("%s%02x", path, 0))) } - if bt.Right != nil { - ret = fmt.Sprintf("%s%s", ret, bt.Right.toDot(me, fmt.Sprintf("%s%02x", path, 1))) + if bt.right != nil { + ret = fmt.Sprintf("%s%s", ret, bt.right.toDot(me, fmt.Sprintf("%s%02x", path, 1))) } return ret diff --git a/trie/bintrie/internal_node_test.go b/trie/bintrie/internal_node_test.go index 142f1e95c1..976e677177 100644 --- a/trie/bintrie/internal_node_test.go +++ b/trie/bintrie/internal_node_test.go @@ -37,12 +37,12 @@ func TestInternalNodeGet(t *testing.T) { node := &InternalNode{ depth: 0, - Left: &StemNode{ + left: &StemNode{ Stem: leftStem, Values: leftValues[:], depth: 1, }, - Right: &StemNode{ + right: &StemNode{ Stem: rightStem, Values: rightValues[:], depth: 1, @@ -80,8 +80,8 @@ func TestInternalNodeGetWithResolver(t *testing.T) { node := &InternalNode{ depth: 0, - Left: hashedChild, - Right: Empty{}, + left: hashedChild, + right: Empty{}, } // Mock resolver that returns a stem node @@ -119,8 +119,8 @@ func TestInternalNodeInsert(t *testing.T) { // Start with an internal node with empty children node := &InternalNode{ depth: 0, - Left: Empty{}, - Right: Empty{}, + left: Empty{}, + right: Empty{}, } // Insert a value into the left subtree @@ -139,9 +139,9 @@ func TestInternalNodeInsert(t *testing.T) { } // Check that left child is now a StemNode - leftStem, ok := internalNode.Left.(*StemNode) + leftStem, ok := internalNode.left.(*StemNode) 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 @@ -150,9 +150,9 @@ func TestInternalNodeInsert(t *testing.T) { } // Right child should still be Empty - _, ok = internalNode.Right.(Empty) + _, ok = internalNode.right.(Empty) 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{ depth: 0, - Left: leftStem, - Right: rightStem, + left: leftStem, + right: rightStem, } // Create a copy @@ -193,14 +193,14 @@ func TestInternalNodeCopy(t *testing.T) { } // Check that children are copied - copiedLeft, ok := copiedInternal.Left.(*StemNode) + copiedLeft, ok := copiedInternal.left.(*StemNode) 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 { - 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) @@ -225,8 +225,8 @@ func TestInternalNodeHash(t *testing.T) { // Create an internal node node := &InternalNode{ depth: 0, - Left: HashedNode(common.HexToHash("0x1111")), - Right: HashedNode(common.HexToHash("0x2222")), + left: HashedNode(common.HexToHash("0x1111")), + right: HashedNode(common.HexToHash("0x2222")), } hash1 := node.Hash() @@ -238,7 +238,7 @@ func TestInternalNodeHash(t *testing.T) { } // Changing a child should change the hash - node.Left = HashedNode(common.HexToHash("0x3333")) + node.left = HashedNode(common.HexToHash("0x3333")) hash3 := node.Hash() if hash1 == hash3 { 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) nodeWithNil := &InternalNode{ depth: 0, - Left: nil, - Right: HashedNode(common.HexToHash("0x4444")), + left: nil, + right: HashedNode(common.HexToHash("0x4444")), } hashWithNil := nodeWithNil.Hash() if hashWithNil == (common.Hash{}) { @@ -271,12 +271,12 @@ func TestInternalNodeGetValuesAtStem(t *testing.T) { node := &InternalNode{ depth: 0, - Left: &StemNode{ + left: &StemNode{ Stem: leftStem, Values: leftValues[:], depth: 1, }, - Right: &StemNode{ + right: &StemNode{ Stem: rightStem, Values: rightValues[:], depth: 1, @@ -313,8 +313,8 @@ func TestInternalNodeInsertValuesAtStem(t *testing.T) { // Start with an internal node with empty children node := &InternalNode{ depth: 0, - Left: Empty{}, - Right: Empty{}, + left: Empty{}, + right: Empty{}, } // 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 - leftStem, ok := internalNode.Left.(*StemNode) + leftStem, ok := internalNode.left.(*StemNode) 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]) { @@ -365,8 +365,8 @@ func TestInternalNodeCollectNodes(t *testing.T) { node := &InternalNode{ depth: 0, - Left: leftStem, - Right: rightStem, + left: leftStem, + right: rightStem, } var collectedPaths [][]byte @@ -410,12 +410,12 @@ func TestInternalNodeGetHeight(t *testing.T) { // Right subtree: depth 1 (stem) leftInternal := &InternalNode{ depth: 1, - Left: &StemNode{ + left: &StemNode{ Stem: make([]byte, 31), Values: make([][]byte, 256), depth: 2, }, - Right: Empty{}, + right: Empty{}, } rightStem := &StemNode{ @@ -426,8 +426,8 @@ func TestInternalNodeGetHeight(t *testing.T) { node := &InternalNode{ depth: 0, - Left: leftInternal, - Right: rightStem, + left: leftInternal, + right: rightStem, } height := node.GetHeight() @@ -443,8 +443,8 @@ func TestInternalNodeDepthTooLarge(t *testing.T) { // Create an internal node at max depth node := &InternalNode{ depth: 31*8 + 1, - Left: Empty{}, - Right: Empty{}, + left: Empty{}, + right: Empty{}, } stem := make([]byte, 31) diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go index 94ce3895df..ebe0162177 100644 --- a/trie/bintrie/iterator.go +++ b/trie/bintrie/iterator.go @@ -69,9 +69,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool { // recurse into both children if context.Index == 0 { - if _, isempty := node.Left.(Empty); node.Left != nil && !isempty { - it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Left}) - it.current = node.Left + if _, isempty := node.left.(Empty); node.left != nil && !isempty { + it.stack = append(it.stack, binaryNodeIteratorState{Node: node.left}) + it.current = node.left return it.Next(descend) } @@ -79,9 +79,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool { } if context.Index == 1 { - if _, isempty := node.Right.(Empty); node.Right != nil && !isempty { - it.stack = append(it.stack, binaryNodeIteratorState{Node: node.Right}) - it.current = node.Right + if _, isempty := node.right.(Empty); node.right != nil && !isempty { + it.stack = append(it.stack, binaryNodeIteratorState{Node: node.right}) + it.current = node.right return it.Next(descend) } @@ -127,9 +127,9 @@ func (it *binaryNodeIterator) Next(descend bool) bool { it.stack[len(it.stack)-1].Node = it.current parent := &it.stack[len(it.stack)-2] if parent.Index == 0 { - parent.Node.(*InternalNode).Left = it.current + parent.Node.(*InternalNode).left = it.current } else { - parent.Node.(*InternalNode).Right = it.current + parent.Node.(*InternalNode).right = it.current } return it.Next(descend) case Empty: @@ -177,8 +177,9 @@ func (it *binaryNodeIterator) Path() []byte { return path } +// NodeBlob returns the serialized bytes of the current node. func (it *binaryNodeIterator) NodeBlob() []byte { - panic("not completely implemented") + return SerializeNode(it.current) } // 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 // to the value after calling Next. func (it *binaryNodeIterator) LeafProof() [][]byte { - _, ok := it.current.(*StemNode) + sn, ok := it.current.(*StemNode) if !ok { panic("LeafProof() called on an binary node iterator not at a leaf location") } - // return it.trie.Prove(leaf.Key()) - panic("not completely implemented") + proof := make([][]byte, 0, len(it.stack)+NodeWidth) + + // 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 diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go index 568d3ff4f2..91f2482fdf 100644 --- a/trie/bintrie/stem_node.go +++ b/trie/bintrie/stem_node.go @@ -23,7 +23,7 @@ import ( "slices" "github.com/ethereum/go-ethereum/common" - "github.com/zeebo/blake3" + "crypto/sha256" ) // 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++ var child, other *BinaryNode if bitStem == 0 { - new.Left = bt - child = &new.Left - other = &new.Right + new.left = bt + child = &new.left + other = &new.right } else { - new.Right = bt - child = &new.Right - other = &new.Left + new.right = bt + child = &new.right + other = &new.left } 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 for i, v := range bt.Values { if v != nil { - h := blake3.Sum256(v) + h := sha256.Sum256(v) data[i] = common.BytesToHash(h[:]) } } - h := blake3.New() + h := sha256.New() for level := 1; level <= 8; level++ { for i := range NodeWidth / (1 << level) { h.Reset() @@ -157,13 +157,13 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv bt.depth++ var child, other *BinaryNode if bitStem == 0 { - new.Left = bt - child = &new.Left - other = &new.Right + new.left = bt + child = &new.left + other = &new.right } else { - new.Right = bt - child = &new.Right - other = &new.Left + new.right = bt + child = &new.right + other = &new.left } bitKey := key[new.depth/8] >> (7 - (new.depth % 8)) & 1 diff --git a/trie/bintrie/stem_node_test.go b/trie/bintrie/stem_node_test.go index 47dda38f59..4eedb43492 100644 --- a/trie/bintrie/stem_node_test.go +++ b/trie/bintrie/stem_node_test.go @@ -103,18 +103,18 @@ func TestStemNodeInsertDifferentStem(t *testing.T) { } // Original stem should be on the left (bit 0) - leftStem, ok := internalNode.Left.(*StemNode) + leftStem, ok := internalNode.left.(*StemNode) 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) { t.Errorf("Left stem mismatch") } // New stem should be on the right (bit 1) - rightStem, ok := internalNode.Right.(*StemNode) + rightStem, ok := internalNode.right.(*StemNode) 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]) { t.Errorf("Right stem mismatch") diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index 19543bf45c..ca4ac511f8 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -44,6 +44,7 @@ func NewBinaryNode() BinaryNode { type BinaryTrie struct { root BinaryNode reader *trie.TrieReader + tracer *trie.PrevalueTracer } // 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) { 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 { panic(fmt.Errorf("CollectNodes failed: %v", err)) diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go index e6cf795ee5..f0a9c8b8b0 100644 --- a/trie/bintrie/trie_test.go +++ b/trie/bintrie/trie_test.go @@ -42,7 +42,7 @@ func TestSingleEntry(t *testing.T) { if tree.GetHeight() != 1 { t.Fatal("invalid depth") } - expected := common.HexToHash("694545468677064fd833cddc8455762fe6b21c6cabe2fc172529e0f573181cd5") + expected := common.HexToHash("aab1060e04cb4f5dc6f697ae93156a95714debbf77d54238766adc5709282b6f") got := tree.Hash() if 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 { t.Fatal("invalid height") } - if tree.Hash() != common.HexToHash("85fc622076752a6fcda2c886c18058d639066a83473d9684704b5a29455ed2ed") { + if tree.Hash() != common.HexToHash("dfc69c94013a8b3c65395625a719a87534a7cfd38719251ad8c8ea7fe79f065e") { t.Fatal("invalid tree root") } } @@ -190,7 +190,7 @@ func TestMerkleizeMultipleEntries(t *testing.T) { } } got := tree.Hash() - expected := common.HexToHash("8c74de28e6bb6b2296cae37cff16266e2dbf533bc204fa4cb0c237761ae8a2c8") + expected := common.HexToHash("9317155862f7a3867660ddd0966ff799a3d16aa4df1e70a7516eaa4a675191b5") if got != expected { t.Fatalf("invalid root, expected=%x, got = %x", expected, got) } diff --git a/trie/committer.go b/trie/committer.go index a040868c6c..2a2142e0ff 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -29,12 +29,12 @@ import ( // insertion order. type committer struct { nodes *trienode.NodeSet - tracer *prevalueTracer + tracer *PrevalueTracer collectLeaf bool } // 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{ nodes: nodeset, 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 // will not be stored in the database independently, mark it as // deleted only if the node was existent in database before. - origin := c.tracer.get(path) + origin := c.tracer.Get(path) if len(origin) != 0 { 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. 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 // full node since it's impossible to store value in fullNode. The key diff --git a/trie/tracer.go b/trie/tracer.go index b0542404a7..184a513325 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -94,20 +94,19 @@ func (t *opTracer) deletedList() [][]byte { 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 // non-existent and should not occur in practice. // -// Note prevalueTracer is not thread-safe, callers should be responsible for -// handling the concurrency issues by themselves. -type prevalueTracer struct { +// Note PrevalueTracer is thread-safe. +type PrevalueTracer struct { data map[string][]byte lock sync.RWMutex } // newPrevalueTracer initializes the tracer for capturing resolved trie nodes. -func newPrevalueTracer() *prevalueTracer { - return &prevalueTracer{ +func newPrevalueTracer() *PrevalueTracer { + return &PrevalueTracer{ data: make(map[string][]byte), } } @@ -115,16 +114,16 @@ func newPrevalueTracer() *prevalueTracer { // put tracks the newly loaded trie node and caches its RLP-encoded // blob internally. Do not modify the value outside this function, // 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() defer t.lock.Unlock() 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. -func (t *prevalueTracer) get(path []byte) []byte { +func (t *PrevalueTracer) Get(path []byte) []byte { t.lock.RLock() 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 // 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() 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. -func (t *prevalueTracer) values() map[string][]byte { +func (t *PrevalueTracer) values() map[string][]byte { t.lock.RLock() defer t.lock.RUnlock() @@ -154,7 +153,7 @@ func (t *prevalueTracer) values() map[string][]byte { } // reset resets the cached content in the prevalueTracer. -func (t *prevalueTracer) reset() { +func (t *PrevalueTracer) reset() { t.lock.Lock() defer t.lock.Unlock() @@ -162,12 +161,12 @@ func (t *prevalueTracer) reset() { } // copy returns a copied prevalueTracer instance. -func (t *prevalueTracer) copy() *prevalueTracer { +func (t *PrevalueTracer) copy() *PrevalueTracer { t.lock.RLock() defer t.lock.RUnlock() // Shadow clone is used, as the cached trie node values are immutable - return &prevalueTracer{ + return &PrevalueTracer{ data: maps.Clone(t.data), } } diff --git a/trie/trie.go b/trie/trie.go index 90b06b51df..9ff13c50c0 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -59,7 +59,7 @@ type Trie struct { // Various tracers for capturing the modifications to trie opTracer *opTracer - prevalueTracer *prevalueTracer + prevalueTracer *PrevalueTracer } // 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) 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) } @@ -729,7 +729,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { } nodes := trienode.NewNodeSet(t.owner) 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 t.root = newCommitter(nodes, t.prevalueTracer, collectLeaf).Commit(t.root, t.uncommitted > 100) diff --git a/trie/verkle.go b/trie/verkle.go index 8233aded38..4922e957da 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -42,7 +42,7 @@ type VerkleTrie struct { root verkle.VerkleNode cache *utils.PointCache reader *TrieReader - tracer *prevalueTracer + tracer *PrevalueTracer } // 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{}) for _, node := range nodes { // 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 return t.Hash(), nodeset