Skip to content

Commit bd4b179

Browse files
gballetparithoshrjl493456442
authored
trie/bintrie: add eip7864 binary trees and run its tests (#32365)
Implement the binary tree as specified in [eip-7864](https://eips.ethereum.org/EIPS/eip-7864). This will gradually replace verkle trees in the codebase. This is only running the tests and will not be executed in production, but will help me rebase some of my work, so that it doesn't bitrot as much. --------- Signed-off-by: Guillaume Ballet Co-authored-by: Parithosh Jayanthi <parithosh.jayanthi@ethereum.org> Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
1 parent 931befe commit bd4b179

23 files changed

+3140
-59
lines changed

core/types/hashes.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ var (
4545

4646
// EmptyVerkleHash is the known hash of an empty verkle trie.
4747
EmptyVerkleHash = common.Hash{}
48+
49+
// EmptyBinaryHash is the known hash of an empty binary trie.
50+
EmptyBinaryHash = common.Hash{}
4851
)

trie/bintrie/binary_node.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2025 go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package bintrie
18+
19+
import (
20+
"errors"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
)
24+
25+
type (
26+
NodeFlushFn func([]byte, BinaryNode)
27+
NodeResolverFn func([]byte, common.Hash) ([]byte, error)
28+
)
29+
30+
// zero is the zero value for a 32-byte array.
31+
var zero [32]byte
32+
33+
const (
34+
NodeWidth = 256 // Number of child per leaf node
35+
StemSize = 31 // Number of bytes to travel before reaching a group of leaves
36+
)
37+
38+
const (
39+
nodeTypeStem = iota + 1 // Stem node, contains a stem and a bitmap of values
40+
nodeTypeInternal
41+
)
42+
43+
// BinaryNode is an interface for a binary trie node.
44+
type BinaryNode interface {
45+
Get([]byte, NodeResolverFn) ([]byte, error)
46+
Insert([]byte, []byte, NodeResolverFn, int) (BinaryNode, error)
47+
Copy() BinaryNode
48+
Hash() common.Hash
49+
GetValuesAtStem([]byte, NodeResolverFn) ([][]byte, error)
50+
InsertValuesAtStem([]byte, [][]byte, NodeResolverFn, int) (BinaryNode, error)
51+
CollectNodes([]byte, NodeFlushFn) error
52+
53+
toDot(parent, path string) string
54+
GetHeight() int
55+
}
56+
57+
// SerializeNode serializes a binary trie node into a byte slice.
58+
func SerializeNode(node BinaryNode) []byte {
59+
switch n := (node).(type) {
60+
case *InternalNode:
61+
var serialized [65]byte
62+
serialized[0] = nodeTypeInternal
63+
copy(serialized[1:33], n.left.Hash().Bytes())
64+
copy(serialized[33:65], n.right.Hash().Bytes())
65+
return serialized[:]
66+
case *StemNode:
67+
var serialized [32 + 32 + 256*32]byte
68+
serialized[0] = nodeTypeStem
69+
copy(serialized[1:32], node.(*StemNode).Stem)
70+
bitmap := serialized[32:64]
71+
offset := 64
72+
for i, v := range node.(*StemNode).Values {
73+
if v != nil {
74+
bitmap[i/8] |= 1 << (7 - (i % 8))
75+
copy(serialized[offset:offset+32], v)
76+
offset += 32
77+
}
78+
}
79+
return serialized[:]
80+
default:
81+
panic("invalid node type")
82+
}
83+
}
84+
85+
var invalidSerializedLength = errors.New("invalid serialized node length")
86+
87+
// DeserializeNode deserializes a binary trie node from a byte slice.
88+
func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) {
89+
if len(serialized) == 0 {
90+
return Empty{}, nil
91+
}
92+
93+
switch serialized[0] {
94+
case nodeTypeInternal:
95+
if len(serialized) != 65 {
96+
return nil, invalidSerializedLength
97+
}
98+
return &InternalNode{
99+
depth: depth,
100+
left: HashedNode(common.BytesToHash(serialized[1:33])),
101+
right: HashedNode(common.BytesToHash(serialized[33:65])),
102+
}, nil
103+
case nodeTypeStem:
104+
if len(serialized) < 64 {
105+
return nil, invalidSerializedLength
106+
}
107+
var values [256][]byte
108+
bitmap := serialized[32:64]
109+
offset := 64
110+
111+
for i := range 256 {
112+
if bitmap[i/8]>>(7-(i%8))&1 == 1 {
113+
if len(serialized) < offset+32 {
114+
return nil, invalidSerializedLength
115+
}
116+
values[i] = serialized[offset : offset+32]
117+
offset += 32
118+
}
119+
}
120+
return &StemNode{
121+
Stem: serialized[1:32],
122+
Values: values[:],
123+
depth: depth,
124+
}, nil
125+
default:
126+
return nil, errors.New("invalid node type")
127+
}
128+
}
129+
130+
// ToDot converts the binary trie to a DOT language representation. Useful for debugging.
131+
func ToDot(root BinaryNode) string {
132+
return root.toDot("", "")
133+
}

trie/bintrie/binary_node_test.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright 2025 go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package bintrie
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
"github.com/ethereum/go-ethereum/common"
24+
)
25+
26+
// TestSerializeDeserializeInternalNode tests serialization and deserialization of InternalNode
27+
func TestSerializeDeserializeInternalNode(t *testing.T) {
28+
// Create an internal node with two hashed children
29+
leftHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
30+
rightHash := common.HexToHash("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321")
31+
32+
node := &InternalNode{
33+
depth: 5,
34+
left: HashedNode(leftHash),
35+
right: HashedNode(rightHash),
36+
}
37+
38+
// Serialize the node
39+
serialized := SerializeNode(node)
40+
41+
// Check the serialized format
42+
if serialized[0] != nodeTypeInternal {
43+
t.Errorf("Expected type byte to be %d, got %d", nodeTypeInternal, serialized[0])
44+
}
45+
46+
if len(serialized) != 65 {
47+
t.Errorf("Expected serialized length to be 65, got %d", len(serialized))
48+
}
49+
50+
// Deserialize the node
51+
deserialized, err := DeserializeNode(serialized, 5)
52+
if err != nil {
53+
t.Fatalf("Failed to deserialize node: %v", err)
54+
}
55+
56+
// Check that it's an internal node
57+
internalNode, ok := deserialized.(*InternalNode)
58+
if !ok {
59+
t.Fatalf("Expected InternalNode, got %T", deserialized)
60+
}
61+
62+
// Check the depth
63+
if internalNode.depth != 5 {
64+
t.Errorf("Expected depth 5, got %d", internalNode.depth)
65+
}
66+
67+
// Check the left and right hashes
68+
if internalNode.left.Hash() != leftHash {
69+
t.Errorf("Left hash mismatch: expected %x, got %x", leftHash, internalNode.left.Hash())
70+
}
71+
72+
if internalNode.right.Hash() != rightHash {
73+
t.Errorf("Right hash mismatch: expected %x, got %x", rightHash, internalNode.right.Hash())
74+
}
75+
}
76+
77+
// TestSerializeDeserializeStemNode tests serialization and deserialization of StemNode
78+
func TestSerializeDeserializeStemNode(t *testing.T) {
79+
// Create a stem node with some values
80+
stem := make([]byte, 31)
81+
for i := range stem {
82+
stem[i] = byte(i)
83+
}
84+
85+
var values [256][]byte
86+
// Add some values at different indices
87+
values[0] = common.HexToHash("0x0101010101010101010101010101010101010101010101010101010101010101").Bytes()
88+
values[10] = common.HexToHash("0x0202020202020202020202020202020202020202020202020202020202020202").Bytes()
89+
values[255] = common.HexToHash("0x0303030303030303030303030303030303030303030303030303030303030303").Bytes()
90+
91+
node := &StemNode{
92+
Stem: stem,
93+
Values: values[:],
94+
depth: 10,
95+
}
96+
97+
// Serialize the node
98+
serialized := SerializeNode(node)
99+
100+
// Check the serialized format
101+
if serialized[0] != nodeTypeStem {
102+
t.Errorf("Expected type byte to be %d, got %d", nodeTypeStem, serialized[0])
103+
}
104+
105+
// Check the stem is correctly serialized
106+
if !bytes.Equal(serialized[1:32], stem) {
107+
t.Errorf("Stem mismatch in serialized data")
108+
}
109+
110+
// Deserialize the node
111+
deserialized, err := DeserializeNode(serialized, 10)
112+
if err != nil {
113+
t.Fatalf("Failed to deserialize node: %v", err)
114+
}
115+
116+
// Check that it's a stem node
117+
stemNode, ok := deserialized.(*StemNode)
118+
if !ok {
119+
t.Fatalf("Expected StemNode, got %T", deserialized)
120+
}
121+
122+
// Check the stem
123+
if !bytes.Equal(stemNode.Stem, stem) {
124+
t.Errorf("Stem mismatch after deserialization")
125+
}
126+
127+
// Check the values
128+
if !bytes.Equal(stemNode.Values[0], values[0]) {
129+
t.Errorf("Value at index 0 mismatch")
130+
}
131+
if !bytes.Equal(stemNode.Values[10], values[10]) {
132+
t.Errorf("Value at index 10 mismatch")
133+
}
134+
if !bytes.Equal(stemNode.Values[255], values[255]) {
135+
t.Errorf("Value at index 255 mismatch")
136+
}
137+
138+
// Check that other values are nil
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+
}
146+
}
147+
}
148+
149+
// TestDeserializeEmptyNode tests deserialization of empty node
150+
func TestDeserializeEmptyNode(t *testing.T) {
151+
// Empty byte slice should deserialize to Empty node
152+
deserialized, err := DeserializeNode([]byte{}, 0)
153+
if err != nil {
154+
t.Fatalf("Failed to deserialize empty node: %v", err)
155+
}
156+
157+
_, ok := deserialized.(Empty)
158+
if !ok {
159+
t.Fatalf("Expected Empty node, got %T", deserialized)
160+
}
161+
}
162+
163+
// TestDeserializeInvalidType tests deserialization with invalid type byte
164+
func TestDeserializeInvalidType(t *testing.T) {
165+
// Create invalid serialized data with unknown type byte
166+
invalidData := []byte{99, 0, 0, 0} // Type byte 99 is invalid
167+
168+
_, err := DeserializeNode(invalidData, 0)
169+
if err == nil {
170+
t.Fatal("Expected error for invalid type byte, got nil")
171+
}
172+
}
173+
174+
// TestDeserializeInvalidLength tests deserialization with invalid data length
175+
func TestDeserializeInvalidLength(t *testing.T) {
176+
// InternalNode with type byte 1 but wrong length
177+
invalidData := []byte{nodeTypeInternal, 0, 0} // Too short for internal node
178+
179+
_, err := DeserializeNode(invalidData, 0)
180+
if err == nil {
181+
t.Fatal("Expected error for invalid data length, got nil")
182+
}
183+
184+
if err.Error() != "invalid serialized node length" {
185+
t.Errorf("Expected 'invalid serialized node length' error, got: %v", err)
186+
}
187+
}
188+
189+
// TestKeyToPath tests the keyToPath function
190+
func TestKeyToPath(t *testing.T) {
191+
tests := []struct {
192+
name string
193+
depth int
194+
key []byte
195+
expected []byte
196+
wantErr bool
197+
}{
198+
{
199+
name: "depth 0",
200+
depth: 0,
201+
key: []byte{0x80}, // 10000000 in binary
202+
expected: []byte{1},
203+
wantErr: false,
204+
},
205+
{
206+
name: "depth 7",
207+
depth: 7,
208+
key: []byte{0xFF}, // 11111111 in binary
209+
expected: []byte{1, 1, 1, 1, 1, 1, 1, 1},
210+
wantErr: false,
211+
},
212+
{
213+
name: "depth crossing byte boundary",
214+
depth: 10,
215+
key: []byte{0xFF, 0x00}, // 11111111 00000000 in binary
216+
expected: []byte{1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
217+
wantErr: false,
218+
},
219+
{
220+
name: "max valid depth",
221+
depth: 31 * 8,
222+
key: make([]byte, 32),
223+
expected: make([]byte, 31*8+1),
224+
wantErr: false,
225+
},
226+
{
227+
name: "depth too large",
228+
depth: 31*8 + 1,
229+
key: make([]byte, 32),
230+
wantErr: true,
231+
},
232+
}
233+
234+
for _, tt := range tests {
235+
t.Run(tt.name, func(t *testing.T) {
236+
path, err := keyToPath(tt.depth, tt.key)
237+
if tt.wantErr {
238+
if err == nil {
239+
t.Errorf("Expected error for depth %d, got nil", tt.depth)
240+
}
241+
return
242+
}
243+
if err != nil {
244+
t.Errorf("Unexpected error: %v", err)
245+
return
246+
}
247+
if !bytes.Equal(path, tt.expected) {
248+
t.Errorf("Path mismatch: expected %v, got %v", tt.expected, path)
249+
}
250+
})
251+
}
252+
}

0 commit comments

Comments
 (0)