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.
|
||||
func (t *Trie) newSubTrieMany(prefix []byte, kv []keyValue, value []byte) (Node, int, error) {
|
||||
if len(kv[0].key) == 0 {
|
||||
if len(kv[0].value) == 0 {
|
||||
if kv[0].value == nil {
|
||||
if len(kv) == 1 {
|
||||
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.
|
||||
b := NewBranchNode()
|
||||
if len(value) != 0 {
|
||||
if value != nil {
|
||||
// Empty key is always first.
|
||||
leaf := NewLeafNode(value)
|
||||
t.addRef(leaf.Hash(), leaf.bytes)
|
||||
|
|
|
@ -31,10 +31,18 @@ func testIncompletePut(t *testing.T, ps pairs, n int, tr1, tr2 *Trie) {
|
|||
var b Batch
|
||||
for i, p := range ps {
|
||||
if i < n {
|
||||
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 {
|
||||
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])
|
||||
}
|
||||
|
||||
|
@ -80,6 +88,11 @@ func TestTrie_PutBatchLeaf(t *testing.T) {
|
|||
var ps = pairs{{[]byte{0}, nil}}
|
||||
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) {
|
||||
tr1, tr2 := prepareLeaf(t)
|
||||
var ps = pairs{{[]byte{0}, []byte("replace")}}
|
||||
|
@ -93,6 +106,14 @@ func TestTrie_PutBatchLeaf(t *testing.T) {
|
|||
}
|
||||
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) {
|
||||
|
@ -132,6 +153,11 @@ func TestTrie_PutBatchExtension(t *testing.T) {
|
|||
var ps = pairs{{[]byte{1, 2}, nil}}
|
||||
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) {
|
||||
tr1, tr2 := prepareExtension(t)
|
||||
var ps = pairs{
|
||||
|
|
|
@ -72,10 +72,11 @@ func TestCompatibility(t *testing.T) {
|
|||
|
||||
require.Equal(t, mainTrie.root.Hash(), tr.root.Hash())
|
||||
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([]byte{0x01}, make([]byte, MaxValueLength+1)))
|
||||
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}))
|
||||
})
|
||||
|
||||
|
@ -329,6 +330,18 @@ func TestCompatibility(t *testing.T) {
|
|||
tr1.Flush()
|
||||
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 {
|
||||
|
|
|
@ -103,9 +103,9 @@ func (t *Trie) Put(key, value []byte) error {
|
|||
return errors.New("key is too big")
|
||||
} else if len(value) > MaxValueLength {
|
||||
return errors.New("value is too big")
|
||||
}
|
||||
if len(value) == 0 {
|
||||
return t.Delete(key)
|
||||
} else if value == nil {
|
||||
// (t *Trie).Delete should be used to remove value
|
||||
return errors.New("value is nil")
|
||||
}
|
||||
path := toNibbles(key)
|
||||
n := NewLeafNode(value)
|
||||
|
|
|
@ -18,6 +18,9 @@ func newTestTrie(t *testing.T) *Trie {
|
|||
l1 := NewLeafNode([]byte{0xAB, 0xCD})
|
||||
b.Children[0] = NewExtensionNode([]byte{0x01}, l1)
|
||||
|
||||
l3 := NewLeafNode([]byte{})
|
||||
b.Children[1] = NewExtensionNode([]byte{0x03}, l3)
|
||||
|
||||
l2 := NewLeafNode([]byte{0x22, 0x22})
|
||||
b.Children[9] = NewExtensionNode([]byte{0x09}, l2)
|
||||
|
||||
|
@ -32,8 +35,10 @@ func newTestTrie(t *testing.T) *Trie {
|
|||
tr.putToStore(b)
|
||||
tr.putToStore(l1)
|
||||
tr.putToStore(l2)
|
||||
tr.putToStore(l3)
|
||||
tr.putToStore(v)
|
||||
tr.putToStore(b.Children[0])
|
||||
tr.putToStore(b.Children[1])
|
||||
tr.putToStore(b.Children[9])
|
||||
tr.putToStore(b.Children[10])
|
||||
|
||||
|
@ -79,6 +84,7 @@ func TestTrie_Refcount(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTrie_PutIntoBranchNode(t *testing.T) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
b := NewBranchNode()
|
||||
l := NewLeafNode([]byte{0x8})
|
||||
b.Children[0x7] = NewHashNode(l.Hash())
|
||||
|
@ -86,36 +92,54 @@ func TestTrie_PutIntoBranchNode(t *testing.T) {
|
|||
tr := NewTrie(b, false, newTestStore())
|
||||
|
||||
// empty hash node child
|
||||
require.NoError(t, tr.Put([]byte{0x66}, []byte{0x56}))
|
||||
tr.testHas(t, []byte{0x66}, []byte{0x56})
|
||||
require.NoError(t, tr.Put([]byte{0x66}, value))
|
||||
tr.testHas(t, []byte{0x66}, value)
|
||||
require.True(t, isValid(tr.root))
|
||||
|
||||
// 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))
|
||||
|
||||
// hash is in store
|
||||
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))
|
||||
}
|
||||
|
||||
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) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
l := NewLeafNode([]byte{0x11})
|
||||
key := []byte{0x12}
|
||||
e := NewExtensionNode(toNibbles(key), NewHashNode(l.Hash()))
|
||||
tr := NewTrie(e, false, newTestStore())
|
||||
|
||||
// missing hash
|
||||
require.Error(t, tr.Put(key, []byte{0x42}))
|
||||
require.Error(t, tr.Put(key, value))
|
||||
|
||||
tr.putToStore(l)
|
||||
require.NoError(t, tr.Put(key, []byte{0x42}))
|
||||
tr.testHas(t, key, []byte{0x42})
|
||||
require.NoError(t, tr.Put(key, value))
|
||||
tr.testHas(t, key, value)
|
||||
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) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
b := NewBranchNode()
|
||||
l := NewLeafNode(random.Bytes(5))
|
||||
e := NewExtensionNode([]byte{0x02}, l)
|
||||
|
@ -132,11 +156,19 @@ func TestTrie_PutIntoHashNode(t *testing.T) {
|
|||
|
||||
tr.putToStore(l)
|
||||
|
||||
val := random.Bytes(3)
|
||||
require.NoError(t, tr.Put([]byte{0x12, 0x34}, val))
|
||||
tr.testHas(t, []byte{0x12, 0x34}, val)
|
||||
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{})
|
||||
})
|
||||
}
|
||||
|
||||
func TestTrie_Put(t *testing.T) {
|
||||
|
@ -144,6 +176,7 @@ func TestTrie_Put(t *testing.T) {
|
|||
|
||||
trAct := NewTrie(nil, false, newTestStore())
|
||||
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, 0xAE}, []byte("hello")))
|
||||
|
||||
|
@ -194,9 +227,15 @@ func TestTrie_BigPut(t *testing.T) {
|
|||
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) {
|
||||
k := []byte(items[1].k)
|
||||
require.NoError(t, tr.Put(k, []byte{}))
|
||||
require.NoError(t, tr.Delete(k))
|
||||
tr.testHas(t, k, nil)
|
||||
})
|
||||
}
|
||||
|
@ -319,34 +358,51 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
|||
})
|
||||
|
||||
t.Run("Leaf", func(t *testing.T) {
|
||||
l := NewLeafNode([]byte{0x12, 0x34})
|
||||
check := func(t *testing.T, value []byte) {
|
||||
l := NewLeafNode(value)
|
||||
tr := NewTrie(l, enableGC, newTestStore())
|
||||
t.Run("NonExistentKey", func(t *testing.T) {
|
||||
require.NoError(t, tr.Delete([]byte{0x12}))
|
||||
tr.testHas(t, []byte{}, []byte{0x12, 0x34})
|
||||
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{})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Extension", func(t *testing.T) {
|
||||
t.Run("SingleKey", func(t *testing.T) {
|
||||
l := NewLeafNode([]byte{0x12, 0x34})
|
||||
check := func(t *testing.T, value []byte) {
|
||||
l := NewLeafNode(value)
|
||||
e := NewExtensionNode([]byte{0x0A, 0x0B}, l)
|
||||
tr := NewTrie(e, enableGC, newTestStore())
|
||||
|
||||
t.Run("NonExistentKey", func(t *testing.T) {
|
||||
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{})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("MultipleKeys", func(t *testing.T) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
b := NewBranchNode()
|
||||
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x12, 0x34}))
|
||||
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode(value))
|
||||
b.Children[6] = NewExtensionNode([]byte{0x07}, NewLeafNode([]byte{0x56, 0x78}))
|
||||
e := NewExtensionNode([]byte{0x01, 0x02}, b)
|
||||
tr := NewTrie(e, enableGC, newTestStore())
|
||||
|
@ -359,48 +415,67 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
|||
require.NotEqual(t, h, tr.root.Hash())
|
||||
require.Equal(t, toNibbles([]byte{0x12, 0x67}), e.key)
|
||||
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("3 Children", func(t *testing.T) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
b := NewBranchNode()
|
||||
b.Children[lastChild] = NewLeafNode([]byte{0x12})
|
||||
b.Children[0] = NewExtensionNode([]byte{0x01}, NewLeafNode([]byte{0x34}))
|
||||
b.Children[1] = NewExtensionNode([]byte{0x06}, NewLeafNode([]byte{0x56}))
|
||||
b.Children[1] = NewExtensionNode([]byte{0x06}, NewLeafNode(value))
|
||||
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("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) {
|
||||
newt := func(t *testing.T) *Trie {
|
||||
t.Run("DeleteLast", func(t *testing.T) {
|
||||
t.Run("MergeExtension", func(t *testing.T) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
b := NewBranchNode()
|
||||
b.Children[lastChild] = NewLeafNode([]byte{0x12})
|
||||
b.Children[lastChild] = NewLeafNode(value)
|
||||
l := NewLeafNode([]byte{0x34})
|
||||
e := NewExtensionNode([]byte{0x06}, l)
|
||||
b.Children[5] = NewHashNode(e.Hash())
|
||||
tr := NewTrie(b, enableGC, newTestStore())
|
||||
tr.putToStore(l)
|
||||
tr.putToStore(e)
|
||||
return tr
|
||||
}
|
||||
|
||||
t.Run("DeleteLast", func(t *testing.T) {
|
||||
t.Run("MergeExtension", func(t *testing.T) {
|
||||
tr := newt(t)
|
||||
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) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
ch := NewLeafNode([]byte{5, 6})
|
||||
h := ch.Hash()
|
||||
|
||||
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)
|
||||
|
||||
tr := NewTrie(NewExtensionNode([]byte{1, 2}, b), enableGC, newTestStore())
|
||||
|
@ -411,16 +486,24 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
|||
tr.testHas(t, []byte{0x12}, []byte{5, 6})
|
||||
require.IsType(t, (*ExtensionNode)(nil), tr.root)
|
||||
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) {
|
||||
check := func(t *testing.T, value []byte) {
|
||||
c := NewBranchNode()
|
||||
c.Children[5] = NewLeafNode([]byte{0x05})
|
||||
c.Children[6] = NewLeafNode([]byte{0x06})
|
||||
|
||||
b := NewBranchNode()
|
||||
b.Children[lastChild] = NewLeafNode([]byte{0x12})
|
||||
b.Children[lastChild] = NewLeafNode(value)
|
||||
b.Children[5] = c
|
||||
tr := NewTrie(b, enableGC, newTestStore())
|
||||
|
||||
|
@ -429,15 +512,37 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
|||
tr.testHas(t, []byte{0x55}, []byte{0x05})
|
||||
tr.testHas(t, []byte{0x56}, []byte{0x06})
|
||||
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) {
|
||||
tr := newt(t)
|
||||
check := func(t *testing.T, value []byte) {
|
||||
b := NewBranchNode()
|
||||
b.Children[lastChild] = NewLeafNode([]byte{0x12})
|
||||
l := NewLeafNode(value)
|
||||
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)
|
||||
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("EmptyNode", func(t *testing.T) {
|
||||
tr := NewTrie(EmptyNode{}, false, newTestStore())
|
||||
|
|
Loading…
Reference in a new issue