Skip to content

Commit c5db47c

Browse files
committed
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 <[email protected]>
1 parent f13dfc5 commit c5db47c

17 files changed

+183
-217
lines changed

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ require (
6262
github.com/supranational/blst v0.3.14
6363
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
6464
github.com/urfave/cli/v2 v2.27.5
65-
github.com/zeebo/blake3 v0.2.4
6665
go.uber.org/automaxprocs v1.5.2
6766
go.uber.org/goleak v1.3.0
6867
golang.org/x/crypto v0.36.0

go.sum

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -365,12 +365,6 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
365365
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
366366
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
367367
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
368-
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
369-
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
370-
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
371-
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
372-
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
373-
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
374368
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
375369
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
376370
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=

trie/bintrie/binary_node.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ const (
3535
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
3636
)
3737

38+
const (
39+
nodeTypeStem = iota + 1 // Stem node, contains a stem and a bitmap of values
40+
nodeTypeInternal
41+
)
42+
3843
// BinaryNode is an interface for a binary trie node.
3944
type BinaryNode interface {
4045
Get([]byte, NodeResolverFn) ([]byte, error)
@@ -55,13 +60,13 @@ func SerializeNode(node BinaryNode) []byte {
5560
switch n := (node).(type) {
5661
case *InternalNode:
5762
var serialized [65]byte
58-
serialized[0] = 1
59-
copy(serialized[1:33], n.Left.Hash().Bytes())
60-
copy(serialized[33:65], n.Right.Hash().Bytes())
63+
serialized[0] = nodeTypeInternal
64+
copy(serialized[1:33], n.left.Hash().Bytes())
65+
copy(serialized[33:65], n.right.Hash().Bytes())
6166
return serialized[:]
6267
case *StemNode:
6368
var serialized [32 + 256*32]byte
64-
serialized[0] = 2
69+
serialized[0] = nodeTypeStem
6570
copy(serialized[1:32], node.(*StemNode).Stem)
6671
bitmap := serialized[32:64]
6772
offset := 64
@@ -78,29 +83,37 @@ func SerializeNode(node BinaryNode) []byte {
7883
}
7984
}
8085

86+
var invalidSerializedLength = errors.New("invalid serialized node length")
87+
8188
// DeserializeNode deserializes a binary trie node from a byte slice.
8289
func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
8390
if len(serialized) == 0 {
8491
return Empty{}, nil
8592
}
8693

8794
switch serialized[0] {
88-
case 1:
95+
case nodeTypeInternal:
8996
if len(serialized) != 65 {
90-
return nil, errors.New("invalid serialized node length")
97+
return nil, invalidSerializedLength
9198
}
9299
return &InternalNode{
93100
depth: depth,
94-
Left: HashedNode(common.BytesToHash(serialized[1:33])),
95-
Right: HashedNode(common.BytesToHash(serialized[33:65])),
101+
left: HashedNode(common.BytesToHash(serialized[1:33])),
102+
right: HashedNode(common.BytesToHash(serialized[33:65])),
96103
}, nil
97-
case 2:
104+
case nodeTypeStem:
105+
if len(serialized) < 64 {
106+
return nil, invalidSerializedLength
107+
}
98108
var values [256][]byte
99109
bitmap := serialized[32:64]
100110
offset := 64
101111

102112
for i := range 256 {
103113
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
114+
if len(serialized) < offset+32 {
115+
return nil, invalidSerializedLength
116+
}
104117
values[i] = serialized[offset : offset+32]
105118
offset += 32
106119
}

trie/bintrie/binary_node_test.go

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package bintrie
1818

1919
import (
2020
"bytes"
21-
"errors"
2221
"testing"
2322

2423
"github.com/ethereum/go-ethereum/common"
@@ -32,16 +31,16 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
3231

3332
node := &InternalNode{
3433
depth: 5,
35-
Left: HashedNode(leftHash),
36-
Right: HashedNode(rightHash),
34+
left: HashedNode(leftHash),
35+
right: HashedNode(rightHash),
3736
}
3837

3938
// Serialize the node
4039
serialized := SerializeNode(node)
4140

4241
// Check the serialized format
43-
if serialized[0] != 1 {
44-
t.Errorf("Expected type byte to be 1, got %d", serialized[0])
42+
if serialized[0] != nodeTypeInternal {
43+
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
4544
}
4645

4746
if len(serialized) != 65 {
@@ -66,12 +65,12 @@ func TestSerializeDeserializeInternalNode(t *testing.T) {
6665
}
6766

6867
// Check the left and right hashes
69-
if internalNode.Left.Hash() != leftHash {
70-
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.Left.Hash())
68+
if internalNode.left.Hash() != leftHash {
69+
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.left.Hash())
7170
}
7271

73-
if internalNode.Right.Hash() != rightHash {
74-
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.Right.Hash())
72+
if internalNode.right.Hash() != rightHash {
73+
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.right.Hash())
7574
}
7675
}
7776

@@ -99,8 +98,8 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
9998
serialized := SerializeNode(node)
10099

101100
// Check the serialized format
102-
if serialized[0] != 2 {
103-
t.Errorf("Expected type byte to be 2, got %d", serialized[0])
101+
if serialized[0] != nodeTypeStem {
102+
t.Errorf("Expected type byte to be %d, got %d", nodeTypeStem, serialized[0])
104103
}
105104

106105
// Check the stem is correctly serialized
@@ -137,8 +136,13 @@ func TestSerializeDeserializeStemNode(t *testing.T) {
137136
}
138137

139138
// Check that other values are nil
140-
if stemNode.Values[1] != nil {
141-
t.Errorf("Expected nil value at index 1, got %x", stemNode.Values[1])
139+
for i := range NodeWidth {
140+
if i == 0 || i == 10 || i == 255 {
141+
continue
142+
}
143+
if stemNode.Values[i] != nil {
144+
t.Errorf("Expected nil value at index %d, got %x", i, stemNode.Values[i])
145+
}
142146
}
143147
}
144148

@@ -170,7 +174,7 @@ func TestDeserializeInvalidType(t *testing.T) {
170174
// TestDeserializeInvalidLength tests deserialization with invalid data length
171175
func TestDeserializeInvalidLength(t *testing.T) {
172176
// InternalNode with type byte 1 but wrong length
173-
invalidData := []byte{1, 0, 0} // Too short for internal node
177+
invalidData := []byte{nodeTypeInternal, 0, 0} // Too short for internal node
174178

175179
_, err := DeserializeNode(invalidData, 0)
176180
if err == nil {
@@ -246,24 +250,3 @@ func TestKeyToPath(t *testing.T) {
246250
})
247251
}
248252
}
249-
250-
// Mock resolver function for testing
251-
func mockResolver(path []byte, hash common.Hash) ([]byte, error) {
252-
// Return a simple stem node for testing
253-
if hash == common.HexToHash("0x1234") {
254-
stem := make([]byte, 31)
255-
var values [256][]byte
256-
values[0] = common.HexToHash("0xabcd").Bytes()
257-
node := &StemNode{
258-
Stem: stem,
259-
Values: values[:],
260-
}
261-
return SerializeNode(node), nil
262-
}
263-
return nil, errors.New("node not found")
264-
}
265-
266-
// Mock flush function for testing
267-
func mockFlushFn(path []byte, node BinaryNode) {
268-
// Just a stub for testing
269-
}

trie/bintrie/hashed_node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (h HashedNode) toDot(parent string, path string) string {
5858
}
5959

6060
func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error {
61-
panic("not implemented") // TODO: Implement
61+
return errors.New("collectNodes not implemented for hashed node")
6262
}
6363

6464
func (h HashedNode) GetHeight() int {

trie/bintrie/hashed_node_test.go

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -104,52 +104,6 @@ func TestHashedNodeInsertValuesAtStem(t *testing.T) {
104104
}
105105
}
106106

107-
// TestHashedNodeGet tests that Get panics (as per implementation)
108-
func TestHashedNodeGet(t *testing.T) {
109-
node := HashedNode(common.HexToHash("0x1234"))
110-
111-
defer func() {
112-
if r := recover(); r == nil {
113-
t.Error("Expected panic for Get on HashedNode")
114-
}
115-
}()
116-
117-
key := make([]byte, 32)
118-
_, _ = node.Get(key, nil)
119-
}
120-
121-
// TestHashedNodeCollectNodes tests that CollectNodes panics (as per implementation)
122-
func TestHashedNodeCollectNodes(t *testing.T) {
123-
node := HashedNode(common.HexToHash("0x1234"))
124-
125-
defer func() {
126-
if r := recover(); r == nil {
127-
t.Error("Expected panic for CollectNodes on HashedNode")
128-
}
129-
}()
130-
131-
path := []byte{0, 1, 0}
132-
node.CollectNodes(path, func([]byte, BinaryNode) {})
133-
}
134-
135-
// TestHashedNodeGetHeight tests that GetHeight panics (as per implementation)
136-
func TestHashedNodeGetHeight(t *testing.T) {
137-
node := HashedNode(common.HexToHash("0x1234"))
138-
139-
defer func() {
140-
r := recover()
141-
if r == nil {
142-
t.Error("Expected panic for GetHeight on HashedNode")
143-
}
144-
// Check the panic message
145-
if r != "tried to get the height of a hashed node, this is a bug" {
146-
t.Errorf("Unexpected panic message: %v", r)
147-
}
148-
}()
149-
150-
_ = node.GetHeight()
151-
}
152-
153107
// TestHashedNodeToDot tests the toDot method for visualization
154108
func TestHashedNodeToDot(t *testing.T) {
155109
hash := common.HexToHash("0x1234")
@@ -170,5 +124,5 @@ func TestHashedNodeToDot(t *testing.T) {
170124

171125
// Helper function
172126
func contains(s, substr string) bool {
173-
return len(s) >= len(substr) && s[:len(s)] != "" && len(substr) > 0
127+
return len(s) >= len(substr) && s != "" && len(substr) > 0
174128
}

trie/bintrie/internal_node.go

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"fmt"
2222

2323
"github.com/ethereum/go-ethereum/common"
24-
"github.com/zeebo/blake3"
24+
"crypto/sha256"
2525
)
2626

2727
func keyToPath(depth int, key []byte) ([]byte, error) {
@@ -41,7 +41,7 @@ func keyToPath(depth int, key []byte) ([]byte, error) {
4141

4242
// InternalNode is a binary trie internal node.
4343
type InternalNode struct {
44-
Left, Right BinaryNode
44+
left, right BinaryNode
4545
depth int
4646
}
4747

@@ -54,9 +54,9 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([
5454
bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1
5555
var child *BinaryNode
5656
if bit == 0 {
57-
child = &bt.Left
57+
child = &bt.left
5858
} else {
59-
child = &bt.Right
59+
child = &bt.right
6060
}
6161

6262
if hn, ok := (*child).(HashedNode); ok {
@@ -96,22 +96,22 @@ func (bt *InternalNode) Insert(key []byte, value []byte, resolver NodeResolverFn
9696
// Copy creates a deep copy of the node.
9797
func (bt *InternalNode) Copy() BinaryNode {
9898
return &InternalNode{
99-
Left: bt.Left.Copy(),
100-
Right: bt.Right.Copy(),
99+
left: bt.left.Copy(),
100+
right: bt.right.Copy(),
101101
depth: bt.depth,
102102
}
103103
}
104104

105105
// Hash returns the hash of the node.
106106
func (bt *InternalNode) Hash() common.Hash {
107-
h := blake3.New()
108-
if bt.Left != nil {
109-
h.Write(bt.Left.Hash().Bytes())
107+
h := sha256.New()
108+
if bt.left != nil {
109+
h.Write(bt.left.Hash().Bytes())
110110
} else {
111111
h.Write(zero[:])
112112
}
113-
if bt.Right != nil {
114-
h.Write(bt.Right.Hash().Bytes())
113+
if bt.right != nil {
114+
h.Write(bt.right.Hash().Bytes())
115115
} else {
116116
h.Write(zero[:])
117117
}
@@ -127,9 +127,9 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
127127
err error
128128
)
129129
if bit == 0 {
130-
child = &bt.Left
130+
child = &bt.left
131131
} else {
132-
child = &bt.Right
132+
child = &bt.right
133133
}
134134

135135
*child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1)
@@ -139,21 +139,21 @@ func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolve
139139
// CollectNodes collects all child nodes at a given path, and flushes it
140140
// into the provided node collector.
141141
func (bt *InternalNode) CollectNodes(path []byte, flushfn NodeFlushFn) error {
142-
if bt.Left != nil {
142+
if bt.left != nil {
143143
var p [256]byte
144144
copy(p[:], path)
145145
childpath := p[:len(path)]
146146
childpath = append(childpath, 0)
147-
if err := bt.Left.CollectNodes(childpath, flushfn); err != nil {
147+
if err := bt.left.CollectNodes(childpath, flushfn); err != nil {
148148
return err
149149
}
150150
}
151-
if bt.Right != nil {
151+
if bt.right != nil {
152152
var p [256]byte
153153
copy(p[:], path)
154154
childpath := p[:len(path)]
155155
childpath = append(childpath, 1)
156-
if err := bt.Right.CollectNodes(childpath, flushfn); err != nil {
156+
if err := bt.right.CollectNodes(childpath, flushfn); err != nil {
157157
return err
158158
}
159159
}
@@ -167,11 +167,11 @@ func (bt *InternalNode) GetHeight() int {
167167
leftHeight int
168168
rightHeight int
169169
)
170-
if bt.Left != nil {
171-
leftHeight = bt.Left.GetHeight()
170+
if bt.left != nil {
171+
leftHeight = bt.left.GetHeight()
172172
}
173-
if bt.Right != nil {
174-
rightHeight = bt.Right.GetHeight()
173+
if bt.right != nil {
174+
rightHeight = bt.right.GetHeight()
175175
}
176176
return 1 + max(leftHeight, rightHeight)
177177
}
@@ -183,11 +183,11 @@ func (bt *InternalNode) toDot(parent, path string) string {
183183
ret = fmt.Sprintf("%s %s -> %s\n", ret, parent, me)
184184
}
185185

186-
if bt.Left != nil {
187-
ret = fmt.Sprintf("%s%s", ret, bt.Left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
186+
if bt.left != nil {
187+
ret = fmt.Sprintf("%s%s", ret, bt.left.toDot(me, fmt.Sprintf("%s%02x", path, 0)))
188188
}
189-
if bt.Right != nil {
190-
ret = fmt.Sprintf("%s%s", ret, bt.Right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
189+
if bt.right != nil {
190+
ret = fmt.Sprintf("%s%s", ret, bt.right.toDot(me, fmt.Sprintf("%s%02x", path, 1)))
191191
}
192192

193193
return ret

0 commit comments

Comments
 (0)