core: allow empty MPT Leaf values

Allow it for (*Trie).Put. And distinguish empty value and nil value for
(*Trie).PutBatch, because batch is already capable of handling both nil
and empty value. For (*Trie).PutBatch putting nil value means deletion,
while putting empty value means just putting LeafNode with an empty
value.
This commit is contained in:
Anna Shaleva 2021-08-23 18:32:10 +03:00
parent 1a733ca456
commit f721384ead
5 changed files with 284 additions and 134 deletions

View file

@ -241,7 +241,7 @@ func (t *Trie) putBatchIntoHash(curr *HashNode, kv []keyValue) (Node, int, error
// value is current value stored by prefix. // value is current value stored by prefix.
func (t *Trie) newSubTrieMany(prefix []byte, kv []keyValue, value []byte) (Node, int, error) { func (t *Trie) newSubTrieMany(prefix []byte, kv []keyValue, value []byte) (Node, int, error) {
if len(kv[0].key) == 0 { if len(kv[0].key) == 0 {
if len(kv[0].value) == 0 { if kv[0].value == nil {
if len(kv) == 1 { if len(kv) == 1 {
return EmptyNode{}, 1, nil return EmptyNode{}, 1, nil
} }
@ -256,7 +256,7 @@ func (t *Trie) newSubTrieMany(prefix []byte, kv []keyValue, value []byte) (Node,
// Prefix is empty and we have at least 2 children. // Prefix is empty and we have at least 2 children.
b := NewBranchNode() b := NewBranchNode()
if len(value) != 0 { if value != nil {
// Empty key is always first. // Empty key is always first.
leaf := NewLeafNode(value) leaf := NewLeafNode(value)
t.addRef(leaf.Hash(), leaf.bytes) t.addRef(leaf.Hash(), leaf.bytes)

View file

@ -31,9 +31,17 @@ func testIncompletePut(t *testing.T, ps pairs, n int, tr1, tr2 *Trie) {
var b Batch var b Batch
for i, p := range ps { for i, p := range ps {
if i < n { if i < n {
require.NoError(t, tr1.Put(p[0], p[1]), "item %d", i) if p[1] == nil {
require.NoError(t, tr1.Delete(p[0]), "item %d", i)
} else {
require.NoError(t, tr1.Put(p[0], p[1]), "item %d", i)
}
} else if i == n { } else if i == n {
require.Error(t, tr1.Put(p[0], p[1]), "item %d", i) if p[1] == nil {
require.Error(t, tr1.Delete(p[0]), "item %d", i)
} else {
require.Error(t, tr1.Put(p[0], p[1]), "item %d", i)
}
} }
b.Add(p[0], p[1]) b.Add(p[0], p[1])
} }
@ -80,6 +88,11 @@ func TestTrie_PutBatchLeaf(t *testing.T) {
var ps = pairs{{[]byte{0}, nil}} var ps = pairs{{[]byte{0}, nil}}
testPut(t, ps, tr1, tr2) testPut(t, ps, tr1, tr2)
}) })
t.Run("empty value", func(t *testing.T) {
tr1, tr2 := prepareLeaf(t)
var ps = pairs{{[]byte{0}, []byte{}}}
testPut(t, ps, tr1, tr2)
})
t.Run("replace", func(t *testing.T) { t.Run("replace", func(t *testing.T) {
tr1, tr2 := prepareLeaf(t) tr1, tr2 := prepareLeaf(t)
var ps = pairs{{[]byte{0}, []byte("replace")}} var ps = pairs{{[]byte{0}, []byte("replace")}}
@ -93,6 +106,14 @@ func TestTrie_PutBatchLeaf(t *testing.T) {
} }
testPut(t, ps, tr1, tr2) testPut(t, ps, tr1, tr2)
}) })
t.Run("empty value and replace", func(t *testing.T) {
tr1, tr2 := prepareLeaf(t)
var ps = pairs{
{[]byte{0}, []byte{}},
{[]byte{0, 2}, []byte("replace2")},
}
testPut(t, ps, tr1, tr2)
})
} }
func TestTrie_PutBatchExtension(t *testing.T) { func TestTrie_PutBatchExtension(t *testing.T) {
@ -132,6 +153,11 @@ func TestTrie_PutBatchExtension(t *testing.T) {
var ps = pairs{{[]byte{1, 2}, nil}} var ps = pairs{{[]byte{1, 2}, nil}}
testPut(t, ps, tr1, tr2) testPut(t, ps, tr1, tr2)
}) })
t.Run("empty value", func(t *testing.T) {
tr1, tr2 := prepareExtension(t)
var ps = pairs{{[]byte{1, 2}, []byte{}}}
testPut(t, ps, tr1, tr2)
})
t.Run("add to next, merge extension", func(t *testing.T) { t.Run("add to next, merge extension", func(t *testing.T) {
tr1, tr2 := prepareExtension(t) tr1, tr2 := prepareExtension(t)
var ps = pairs{ var ps = pairs{

View file

@ -72,10 +72,11 @@ func TestCompatibility(t *testing.T) {
require.Equal(t, mainTrie.root.Hash(), tr.root.Hash()) require.Equal(t, mainTrie.root.Hash(), tr.root.Hash())
require.Error(t, tr.Put(nil, []byte{0x01})) require.Error(t, tr.Put(nil, []byte{0x01}))
require.NoError(t, tr.Put([]byte{0x01}, nil)) require.Error(t, tr.Put([]byte{0x01}, nil))
require.Error(t, tr.Put(make([]byte, MaxKeyLength+1), nil)) require.Error(t, tr.Put(make([]byte, MaxKeyLength+1), nil))
require.Error(t, tr.Put([]byte{0x01}, make([]byte, MaxValueLength+1))) require.Error(t, tr.Put([]byte{0x01}, make([]byte, MaxValueLength+1)))
require.Equal(t, mainTrie.root.Hash(), tr.root.Hash()) require.Equal(t, mainTrie.root.Hash(), tr.root.Hash())
require.NoError(t, tr.Put([]byte{0x01}, []byte{}))
require.NoError(t, tr.Put([]byte{0xac, 0x01}, []byte{0xab})) require.NoError(t, tr.Put([]byte{0xac, 0x01}, []byte{0xab}))
}) })
@ -329,6 +330,18 @@ func TestCompatibility(t *testing.T) {
tr1.Flush() tr1.Flush()
checkBatchSize(t, tr1, 7) checkBatchSize(t, tr1, 7)
}) })
t.Run("EmptyValueIssue633", func(t *testing.T) {
tr := newFilledTrie(t,
[]byte{0x01}, []byte{})
tr.Flush()
checkBatchSize(t, tr, 2)
proof := testGetProof(t, tr, []byte{0x01}, 2)
value, ok := VerifyProof(tr.root.Hash(), []byte{0x01}, proof)
require.True(t, ok)
require.Equal(t, []byte{}, value)
})
} }
func copyTrie(t *Trie) *Trie { func copyTrie(t *Trie) *Trie {

View file

@ -103,9 +103,9 @@ func (t *Trie) Put(key, value []byte) error {
return errors.New("key is too big") return errors.New("key is too big")
} else if len(value) > MaxValueLength { } else if len(value) > MaxValueLength {
return errors.New("value is too big") return errors.New("value is too big")
} } else if value == nil {
if len(value) == 0 { // (t *Trie).Delete should be used to remove value
return t.Delete(key) return errors.New("value is nil")
} }
path := toNibbles(key) path := toNibbles(key)
n := NewLeafNode(value) n := NewLeafNode(value)

View file

@ -18,6 +18,9 @@ func newTestTrie(t *testing.T) *Trie {
l1 := NewLeafNode([]byte{0xAB, 0xCD}) l1 := NewLeafNode([]byte{0xAB, 0xCD})
b.Children[0] = NewExtensionNode([]byte{0x01}, l1) b.Children[0] = NewExtensionNode([]byte{0x01}, l1)
l3 := NewLeafNode([]byte{})
b.Children[1] = NewExtensionNode([]byte{0x03}, l3)
l2 := NewLeafNode([]byte{0x22, 0x22}) l2 := NewLeafNode([]byte{0x22, 0x22})
b.Children[9] = NewExtensionNode([]byte{0x09}, l2) b.Children[9] = NewExtensionNode([]byte{0x09}, l2)
@ -32,8 +35,10 @@ func newTestTrie(t *testing.T) *Trie {
tr.putToStore(b) tr.putToStore(b)
tr.putToStore(l1) tr.putToStore(l1)
tr.putToStore(l2) tr.putToStore(l2)
tr.putToStore(l3)
tr.putToStore(v) tr.putToStore(v)
tr.putToStore(b.Children[0]) tr.putToStore(b.Children[0])
tr.putToStore(b.Children[1])
tr.putToStore(b.Children[9]) tr.putToStore(b.Children[9])
tr.putToStore(b.Children[10]) tr.putToStore(b.Children[10])
@ -79,64 +84,91 @@ func TestTrie_Refcount(t *testing.T) {
} }
func TestTrie_PutIntoBranchNode(t *testing.T) { func TestTrie_PutIntoBranchNode(t *testing.T) {
b := NewBranchNode() check := func(t *testing.T, value []byte) {
l := NewLeafNode([]byte{0x8}) b := NewBranchNode()
b.Children[0x7] = NewHashNode(l.Hash()) l := NewLeafNode([]byte{0x8})
b.Children[0x8] = NewHashNode(random.Uint256()) b.Children[0x7] = NewHashNode(l.Hash())
tr := NewTrie(b, false, newTestStore()) b.Children[0x8] = NewHashNode(random.Uint256())
tr := NewTrie(b, false, newTestStore())
// empty hash node child // empty hash node child
require.NoError(t, tr.Put([]byte{0x66}, []byte{0x56})) require.NoError(t, tr.Put([]byte{0x66}, value))
tr.testHas(t, []byte{0x66}, []byte{0x56}) tr.testHas(t, []byte{0x66}, value)
require.True(t, isValid(tr.root)) require.True(t, isValid(tr.root))
// missing hash // missing hash
require.Error(t, tr.Put([]byte{0x70}, []byte{0x42})) require.Error(t, tr.Put([]byte{0x70}, value))
require.True(t, isValid(tr.root)) require.True(t, isValid(tr.root))
// hash is in store // hash is in store
tr.putToStore(l) tr.putToStore(l)
require.NoError(t, tr.Put([]byte{0x70}, []byte{0x42})) require.NoError(t, tr.Put([]byte{0x70}, value))
require.True(t, isValid(tr.root)) require.True(t, isValid(tr.root))
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x42})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
} }
func TestTrie_PutIntoExtensionNode(t *testing.T) { func TestTrie_PutIntoExtensionNode(t *testing.T) {
l := NewLeafNode([]byte{0x11}) check := func(t *testing.T, value []byte) {
key := []byte{0x12} l := NewLeafNode([]byte{0x11})
e := NewExtensionNode(toNibbles(key), NewHashNode(l.Hash())) key := []byte{0x12}
tr := NewTrie(e, false, newTestStore()) e := NewExtensionNode(toNibbles(key), NewHashNode(l.Hash()))
tr := NewTrie(e, false, newTestStore())
// missing hash // missing hash
require.Error(t, tr.Put(key, []byte{0x42})) require.Error(t, tr.Put(key, value))
tr.putToStore(l) tr.putToStore(l)
require.NoError(t, tr.Put(key, []byte{0x42})) require.NoError(t, tr.Put(key, value))
tr.testHas(t, key, []byte{0x42}) tr.testHas(t, key, value)
require.True(t, isValid(tr.root)) require.True(t, isValid(tr.root))
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x42})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
} }
func TestTrie_PutIntoHashNode(t *testing.T) { func TestTrie_PutIntoHashNode(t *testing.T) {
b := NewBranchNode() check := func(t *testing.T, value []byte) {
l := NewLeafNode(random.Bytes(5)) b := NewBranchNode()
e := NewExtensionNode([]byte{0x02}, l) l := NewLeafNode(random.Bytes(5))
b.Children[1] = NewHashNode(e.Hash()) e := NewExtensionNode([]byte{0x02}, l)
b.Children[9] = NewHashNode(random.Uint256()) b.Children[1] = NewHashNode(e.Hash())
tr := NewTrie(b, false, newTestStore()) b.Children[9] = NewHashNode(random.Uint256())
tr := NewTrie(b, false, newTestStore())
tr.putToStore(e) tr.putToStore(e)
t.Run("MissingLeafHash", func(t *testing.T) { t.Run("MissingLeafHash", func(t *testing.T) {
_, err := tr.Get([]byte{0x12}) _, err := tr.Get([]byte{0x12})
require.Error(t, err) require.Error(t, err)
})
tr.putToStore(l)
require.NoError(t, tr.Put([]byte{0x12, 0x34}, value))
tr.testHas(t, []byte{0x12, 0x34}, value)
tr.testHas(t, []byte{0x12}, l.value)
require.True(t, isValid(tr.root))
}
t.Run("non-empty value", func(t *testing.T) {
val := random.Bytes(3)
check(t, val)
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
}) })
tr.putToStore(l)
val := random.Bytes(3)
require.NoError(t, tr.Put([]byte{0x12, 0x34}, val))
tr.testHas(t, []byte{0x12, 0x34}, val)
tr.testHas(t, []byte{0x12}, l.value)
require.True(t, isValid(tr.root))
} }
func TestTrie_Put(t *testing.T) { func TestTrie_Put(t *testing.T) {
@ -144,6 +176,7 @@ func TestTrie_Put(t *testing.T) {
trAct := NewTrie(nil, false, newTestStore()) trAct := NewTrie(nil, false, newTestStore())
require.NoError(t, trAct.Put([]byte{0xAC, 0x01}, []byte{0xAB, 0xCD})) require.NoError(t, trAct.Put([]byte{0xAC, 0x01}, []byte{0xAB, 0xCD}))
require.NoError(t, trAct.Put([]byte{0xAC, 0x13}, []byte{}))
require.NoError(t, trAct.Put([]byte{0xAC, 0x99}, []byte{0x22, 0x22})) require.NoError(t, trAct.Put([]byte{0xAC, 0x99}, []byte{0x22, 0x22}))
require.NoError(t, trAct.Put([]byte{0xAC, 0xAE}, []byte("hello"))) require.NoError(t, trAct.Put([]byte{0xAC, 0xAE}, []byte("hello")))
@ -194,9 +227,15 @@ func TestTrie_BigPut(t *testing.T) {
tr.testHas(t, k, v) tr.testHas(t, k, v)
}) })
t.Run("Rewrite to empty", func(t *testing.T) {
k, v := []byte(items[0].k), []byte{}
require.NoError(t, tr.Put(k, v))
tr.testHas(t, k, v)
})
t.Run("Remove", func(t *testing.T) { t.Run("Remove", func(t *testing.T) {
k := []byte(items[1].k) k := []byte(items[1].k)
require.NoError(t, tr.Put(k, []byte{})) require.NoError(t, tr.Delete(k))
tr.testHas(t, k, nil) tr.testHas(t, k, nil)
}) })
} }
@ -319,125 +358,191 @@ func testTrieDelete(t *testing.T, enableGC bool) {
}) })
t.Run("Leaf", func(t *testing.T) { t.Run("Leaf", func(t *testing.T) {
l := NewLeafNode([]byte{0x12, 0x34}) check := func(t *testing.T, value []byte) {
tr := NewTrie(l, enableGC, newTestStore()) l := NewLeafNode(value)
t.Run("NonExistentKey", func(t *testing.T) { tr := NewTrie(l, enableGC, newTestStore())
require.NoError(t, tr.Delete([]byte{0x12})) t.Run("NonExistentKey", func(t *testing.T) {
tr.testHas(t, []byte{}, []byte{0x12, 0x34}) require.NoError(t, tr.Delete([]byte{0x12}))
tr.testHas(t, []byte{}, value)
})
require.NoError(t, tr.Delete([]byte{}))
tr.testHas(t, []byte{}, nil)
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x12, 0x34})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
}) })
require.NoError(t, tr.Delete([]byte{}))
tr.testHas(t, []byte{}, nil)
}) })
t.Run("Extension", func(t *testing.T) { t.Run("Extension", func(t *testing.T) {
t.Run("SingleKey", func(t *testing.T) { t.Run("SingleKey", func(t *testing.T) {
l := NewLeafNode([]byte{0x12, 0x34}) check := func(t *testing.T, value []byte) {
e := NewExtensionNode([]byte{0x0A, 0x0B}, l) l := NewLeafNode(value)
tr := NewTrie(e, enableGC, newTestStore()) e := NewExtensionNode([]byte{0x0A, 0x0B}, l)
tr := NewTrie(e, enableGC, newTestStore())
t.Run("NonExistentKey", func(t *testing.T) { t.Run("NonExistentKey", func(t *testing.T) {
require.NoError(t, tr.Delete([]byte{})) require.NoError(t, tr.Delete([]byte{}))
tr.testHas(t, []byte{0xAB}, []byte{0x12, 0x34}) tr.testHas(t, []byte{0xAB}, value)
})
require.NoError(t, tr.Delete([]byte{0xAB}))
require.IsType(t, EmptyNode{}, tr.root)
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x12, 0x34})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
}) })
require.NoError(t, tr.Delete([]byte{0xAB}))
require.IsType(t, EmptyNode{}, tr.root)
}) })
t.Run("MultipleKeys", func(t *testing.T) { t.Run("MultipleKeys", func(t *testing.T) {
b := NewBranchNode() check := func(t *testing.T, value []byte) {
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x12, 0x34})) b := NewBranchNode()
b.Children[6] = NewExtensionNode([]byte{0x07}, NewLeafNode([]byte{0x56, 0x78})) b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode(value))
e := NewExtensionNode([]byte{0x01, 0x02}, b) b.Children[6] = NewExtensionNode([]byte{0x07}, NewLeafNode([]byte{0x56, 0x78}))
tr := NewTrie(e, enableGC, newTestStore()) e := NewExtensionNode([]byte{0x01, 0x02}, b)
tr := NewTrie(e, enableGC, newTestStore())
h := e.Hash() h := e.Hash()
require.NoError(t, tr.Delete([]byte{0x12, 0x01})) require.NoError(t, tr.Delete([]byte{0x12, 0x01}))
tr.testHas(t, []byte{0x12, 0x01}, nil) tr.testHas(t, []byte{0x12, 0x01}, nil)
tr.testHas(t, []byte{0x12, 0x67}, []byte{0x56, 0x78}) tr.testHas(t, []byte{0x12, 0x67}, []byte{0x56, 0x78})
require.NotEqual(t, h, tr.root.Hash()) require.NotEqual(t, h, tr.root.Hash())
require.Equal(t, toNibbles([]byte{0x12, 0x67}), e.key) require.Equal(t, toNibbles([]byte{0x12, 0x67}), e.key)
require.IsType(t, (*LeafNode)(nil), e.next) require.IsType(t, (*LeafNode)(nil), e.next)
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x12, 0x34})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
}) })
}) })
t.Run("Branch", func(t *testing.T) { t.Run("Branch", func(t *testing.T) {
t.Run("3 Children", func(t *testing.T) { t.Run("3 Children", func(t *testing.T) {
b := NewBranchNode() check := func(t *testing.T, value []byte) {
b.Children[lastChild] = NewLeafNode([]byte{0x12})
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x34}))
b.Children[1] = NewExtensionNode([]byte{0x06}, NewLeafNode([]byte{0x56}))
tr := NewTrie(b, enableGC, newTestStore())
require.NoError(t, tr.Delete([]byte{0x16}))
tr.testHas(t, []byte{}, []byte{0x12})
tr.testHas(t, []byte{0x01}, []byte{0x34})
tr.testHas(t, []byte{0x16}, nil)
})
t.Run("2 Children", func(t *testing.T) {
newt := func(t *testing.T) *Trie {
b := NewBranchNode() b := NewBranchNode()
b.Children[lastChild] = NewLeafNode([]byte{0x12}) b.Children[lastChild] = NewLeafNode([]byte{0x12})
l := NewLeafNode([]byte{0x34}) b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x34}))
e := NewExtensionNode([]byte{0x06}, l) b.Children[1] = NewExtensionNode([]byte{0x06}, NewLeafNode(value))
b.Children[5] = NewHashNode(e.Hash())
tr := NewTrie(b, enableGC, newTestStore()) tr := NewTrie(b, enableGC, newTestStore())
tr.putToStore(l) require.NoError(t, tr.Delete([]byte{0x16}))
tr.putToStore(e) tr.testHas(t, []byte{}, []byte{0x12})
return tr tr.testHas(t, []byte{0x01}, []byte{0x34})
tr.testHas(t, []byte{0x16}, nil)
} }
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x56})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
})
t.Run("2 Children", func(t *testing.T) {
t.Run("DeleteLast", func(t *testing.T) { t.Run("DeleteLast", func(t *testing.T) {
t.Run("MergeExtension", func(t *testing.T) { t.Run("MergeExtension", func(t *testing.T) {
tr := newt(t) check := func(t *testing.T, value []byte) {
require.NoError(t, tr.Delete([]byte{})) b := NewBranchNode()
tr.testHas(t, []byte{}, nil) b.Children[lastChild] = NewLeafNode(value)
tr.testHas(t, []byte{0x56}, []byte{0x34}) l := NewLeafNode([]byte{0x34})
require.IsType(t, (*ExtensionNode)(nil), tr.root) e := NewExtensionNode([]byte{0x06}, l)
b.Children[5] = NewHashNode(e.Hash())
tr := NewTrie(b, enableGC, newTestStore())
tr.putToStore(l)
tr.putToStore(e)
require.NoError(t, tr.Delete([]byte{}))
tr.testHas(t, []byte{}, nil)
tr.testHas(t, []byte{0x56}, []byte{0x34})
require.IsType(t, (*ExtensionNode)(nil), tr.root)
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x12})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
t.Run("WithHash, branch node replaced", func(t *testing.T) { t.Run("WithHash, branch node replaced", func(t *testing.T) {
ch := NewLeafNode([]byte{5, 6}) check := func(t *testing.T, value []byte) {
h := ch.Hash() ch := NewLeafNode([]byte{5, 6})
h := ch.Hash()
b := NewBranchNode() b := NewBranchNode()
b.Children[3] = NewExtensionNode([]byte{4}, NewLeafNode([]byte{1, 2, 3})) b.Children[3] = NewExtensionNode([]byte{4}, NewLeafNode(value))
b.Children[lastChild] = NewHashNode(h) b.Children[lastChild] = NewHashNode(h)
tr := NewTrie(NewExtensionNode([]byte{1, 2}, b), enableGC, newTestStore()) tr := NewTrie(NewExtensionNode([]byte{1, 2}, b), enableGC, newTestStore())
tr.putToStore(ch) tr.putToStore(ch)
require.NoError(t, tr.Delete([]byte{0x12, 0x34})) require.NoError(t, tr.Delete([]byte{0x12, 0x34}))
tr.testHas(t, []byte{0x12, 0x34}, nil) tr.testHas(t, []byte{0x12, 0x34}, nil)
tr.testHas(t, []byte{0x12}, []byte{5, 6}) tr.testHas(t, []byte{0x12}, []byte{5, 6})
require.IsType(t, (*ExtensionNode)(nil), tr.root) require.IsType(t, (*ExtensionNode)(nil), tr.root)
require.Equal(t, h, tr.root.(*ExtensionNode).next.Hash()) require.Equal(t, h, tr.root.(*ExtensionNode).next.Hash())
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{1, 2, 3})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
}) })
}) })
t.Run("LeaveLeaf", func(t *testing.T) { t.Run("LeaveLeaf", func(t *testing.T) {
c := NewBranchNode() check := func(t *testing.T, value []byte) {
c.Children[5] = NewLeafNode([]byte{0x05}) c := NewBranchNode()
c.Children[6] = NewLeafNode([]byte{0x06}) c.Children[5] = NewLeafNode([]byte{0x05})
c.Children[6] = NewLeafNode([]byte{0x06})
b := NewBranchNode() b := NewBranchNode()
b.Children[lastChild] = NewLeafNode([]byte{0x12}) b.Children[lastChild] = NewLeafNode(value)
b.Children[5] = c b.Children[5] = c
tr := NewTrie(b, enableGC, newTestStore()) tr := NewTrie(b, enableGC, newTestStore())
require.NoError(t, tr.Delete([]byte{})) require.NoError(t, tr.Delete([]byte{}))
tr.testHas(t, []byte{}, nil) tr.testHas(t, []byte{}, nil)
tr.testHas(t, []byte{0x55}, []byte{0x05}) tr.testHas(t, []byte{0x55}, []byte{0x05})
tr.testHas(t, []byte{0x56}, []byte{0x06}) tr.testHas(t, []byte{0x56}, []byte{0x06})
require.IsType(t, (*ExtensionNode)(nil), tr.root) require.IsType(t, (*ExtensionNode)(nil), tr.root)
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x12})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
}) })
}) })
t.Run("DeleteMiddle", func(t *testing.T) { t.Run("DeleteMiddle", func(t *testing.T) {
tr := newt(t) check := func(t *testing.T, value []byte) {
require.NoError(t, tr.Delete([]byte{0x56})) b := NewBranchNode()
tr.testHas(t, []byte{}, []byte{0x12}) b.Children[lastChild] = NewLeafNode([]byte{0x12})
tr.testHas(t, []byte{0x56}, nil) l := NewLeafNode(value)
require.IsType(t, (*LeafNode)(nil), tr.root) e := NewExtensionNode([]byte{0x06}, l)
b.Children[5] = NewHashNode(e.Hash())
tr := NewTrie(b, enableGC, newTestStore())
tr.putToStore(l)
tr.putToStore(e)
require.NoError(t, tr.Delete([]byte{0x56}))
tr.testHas(t, []byte{}, []byte{0x12})
tr.testHas(t, []byte{0x56}, nil)
require.IsType(t, (*LeafNode)(nil), tr.root)
}
t.Run("non-empty value", func(t *testing.T) {
check(t, []byte{0x34})
})
t.Run("empty value", func(t *testing.T) {
check(t, []byte{})
})
}) })
}) })
}) })
@ -503,6 +608,12 @@ func TestTrie_Collapse(t *testing.T) {
tr.Collapse(10) tr.Collapse(10)
require.Equal(t, NewLeafNode([]byte("value")), tr.root) require.Equal(t, NewLeafNode([]byte("value")), tr.root)
}) })
t.Run("Empty Leaf", func(t *testing.T) {
l := NewLeafNode([]byte{})
tr := NewTrie(l, false, newTestStore())
tr.Collapse(10)
require.Equal(t, NewLeafNode([]byte{}), tr.root)
})
t.Run("Hash", func(t *testing.T) { t.Run("Hash", func(t *testing.T) {
t.Run("EmptyNode", func(t *testing.T) { t.Run("EmptyNode", func(t *testing.T) {
tr := NewTrie(EmptyNode{}, false, newTestStore()) tr := NewTrie(EmptyNode{}, false, newTestStore())