mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 23:33:37 +00:00
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:
parent
1a733ca456
commit
f721384ead
5 changed files with 284 additions and 134 deletions
|
@ -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)
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue