Skip to content

Commit d769d1c

Browse files
authored
fix(trie): disallow empty byte slice node values (#2927)
- Always encode empty values for leaf nodes only - Force empty byte slice values to `nil` when inserting - Remove now unneeded `SubValueEqual` method - Add extra test cases
1 parent 36ce37d commit d769d1c

File tree

8 files changed

+79
-85
lines changed

8 files changed

+79
-85
lines changed

internal/trie/node/copy.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ func (n *Node) Copy(settings CopySettings) *Node {
8787
copy(cpy.Key, n.Key)
8888
}
8989

90-
// nil and []byte{} are encoded differently, watch out!
90+
// nil and []byte{} values for branches result in a different node encoding,
91+
// so we ensure to keep the `nil` value.
9192
if settings.CopyValue && n.SubValue != nil {
9293
cpy.SubValue = make([]byte, len(n.SubValue))
9394
copy(cpy.SubValue, n.SubValue)

internal/trie/node/decode_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ func Test_decodeLeaf(t *testing.T) {
327327
errWrapped: ErrDecodeValue,
328328
errMessage: "cannot decode value: unknown prefix for compact uint: 255",
329329
},
330-
"zero value": {
330+
"missing value data": {
331331
reader: bytes.NewBuffer([]byte{
332332
9, // key data
333333
// missing value data
@@ -338,6 +338,17 @@ func Test_decodeLeaf(t *testing.T) {
338338
Key: []byte{9},
339339
},
340340
},
341+
"empty value data": {
342+
reader: bytes.NewBuffer(concatByteSlices([][]byte{
343+
{9}, // key data
344+
scaleEncodeByteSlice(t, nil),
345+
})),
346+
variant: leafVariant.bits,
347+
partialKeyLength: 1,
348+
leaf: &Node{
349+
Key: []byte{9},
350+
},
351+
},
341352
"success": {
342353
reader: bytes.NewBuffer(
343354
concatByteSlices([][]byte{

internal/trie/node/encode.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,19 @@ func (n *Node) Encode(buffer Buffer) (err error) {
3535
return fmt.Errorf("cannot write LE key to buffer: %w", err)
3636
}
3737

38-
if n.Kind() == Branch {
38+
kind := n.Kind()
39+
nodeIsBranch := kind == Branch
40+
if nodeIsBranch {
3941
childrenBitmap := common.Uint16ToBytes(n.ChildrenBitmap())
4042
_, err = buffer.Write(childrenBitmap)
4143
if err != nil {
4244
return fmt.Errorf("cannot write children bitmap to buffer: %w", err)
4345
}
4446
}
4547

46-
// check value is not nil for branch nodes, even though
47-
// leaf nodes always have a non-nil value.
48-
if n.SubValue != nil {
48+
// Only encode node value if the node is a leaf or
49+
// the node is a branch with a non empty value.
50+
if !nodeIsBranch || (nodeIsBranch && n.SubValue != nil) {
4951
encodedValue, err := scale.Marshal(n.SubValue) // TODO scale encoder to write to buffer
5052
if err != nil {
5153
return fmt.Errorf("cannot scale encode value: %w", err)
@@ -57,14 +59,14 @@ func (n *Node) Encode(buffer Buffer) (err error) {
5759
}
5860
}
5961

60-
if n.Kind() == Branch {
62+
if nodeIsBranch {
6163
err = encodeChildrenOpportunisticParallel(n.Children, buffer)
6264
if err != nil {
6365
return fmt.Errorf("cannot encode children of branch: %w", err)
6466
}
6567
}
6668

67-
if n.Kind() == Leaf {
69+
if kind == Leaf {
6870
// TODO cache this for branches too and update test cases.
6971
// TODO remove this copying since it defeats the purpose of `buffer`
7072
// and the sync.Pool.

internal/trie/node/encode_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,25 @@ func Test_Node_Encode(t *testing.T) {
127127
bufferBytesCall: true,
128128
expectedEncoding: []byte{1, 2, 3},
129129
},
130+
"leaf with empty value success": {
131+
node: &Node{
132+
Key: []byte{1, 2, 3},
133+
},
134+
writes: []writeCall{
135+
{
136+
written: []byte{leafVariant.bits | 3}, // partial key length 3
137+
},
138+
{
139+
written: []byte{0x01, 0x23},
140+
},
141+
{
142+
written: []byte{0},
143+
},
144+
},
145+
bufferLenCall: true,
146+
bufferBytesCall: true,
147+
expectedEncoding: []byte{1, 2, 3},
148+
},
130149
"clean branch with encoding": {
131150
node: &Node{
132151
Children: make([]*Node, ChildrenCapacity),
@@ -297,6 +316,32 @@ func Test_Node_Encode(t *testing.T) {
297316
},
298317
},
299318
},
319+
"branch without value and with children success": {
320+
node: &Node{
321+
Key: []byte{1, 2, 3},
322+
Children: []*Node{
323+
nil, nil, nil, {Key: []byte{9}, SubValue: []byte{1}},
324+
nil, nil, nil, {Key: []byte{11}, SubValue: []byte{1}},
325+
},
326+
},
327+
writes: []writeCall{
328+
{ // header
329+
written: []byte{branchVariant.bits | 3}, // partial key length 3
330+
},
331+
{ // key LE
332+
written: []byte{0x01, 0x23},
333+
},
334+
{ // children bitmap
335+
written: []byte{136, 0},
336+
},
337+
{ // first children
338+
written: []byte{16, 65, 9, 4, 1},
339+
},
340+
{ // second children
341+
written: []byte{16, 65, 11, 4, 1},
342+
},
343+
},
344+
},
300345
}
301346

302347
for name, testCase := range testCases {

internal/trie/node/subvalue.go

Lines changed: 0 additions & 17 deletions
This file was deleted.

internal/trie/node/subvalue_test.go

Lines changed: 0 additions & 57 deletions
This file was deleted.

lib/trie/trie.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,11 @@ func (t *Trie) Put(keyLE, value []byte) {
342342

343343
func (t *Trie) insertKeyLE(keyLE, value []byte, deletedMerkleValues map[string]struct{}) {
344344
nibblesKey := codec.KeyLEToNibbles(keyLE)
345+
if len(value) == 0 {
346+
// Force value to be inserted to nil since we don't
347+
// differentiate between nil and empty values.
348+
value = nil
349+
}
345350
t.root, _, _ = t.insert(t.root, nibblesKey, value, deletedMerkleValues)
346351
}
347352

@@ -374,7 +379,7 @@ func (t *Trie) insertInLeaf(parentLeaf *Node, key, value []byte,
374379
newParent *Node, mutated bool, nodesCreated uint32) {
375380
if bytes.Equal(parentLeaf.Key, key) {
376381
nodesCreated = 0
377-
if parentLeaf.SubValueEqual(value) {
382+
if bytes.Equal(parentLeaf.SubValue, value) {
378383
mutated = false
379384
return parentLeaf, mutated, nodesCreated
380385
}
@@ -455,7 +460,7 @@ func (t *Trie) insertInBranch(parentBranch *Node, key, value []byte,
455460
copySettings := node.DefaultCopySettings
456461

457462
if bytes.Equal(key, parentBranch.Key) {
458-
if parentBranch.SubValueEqual(value) {
463+
if bytes.Equal(parentBranch.SubValue, value) {
459464
mutated = false
460465
return parentBranch, mutated, 0
461466
}

lib/trie/trie_endtoend_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ func Fuzz_Trie_PutAndGet_Single(f *testing.F) {
106106
trie := NewEmptyTrie()
107107
trie.Put(key, value)
108108
retrievedValue := trie.Get(key)
109-
assert.Equal(t, retrievedValue, value)
109+
if retrievedValue == nil {
110+
assert.Empty(t, value)
111+
} else {
112+
assert.Equal(t, value, retrievedValue)
113+
}
110114
})
111115
}
112116

0 commit comments

Comments
 (0)