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.
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)

View file

@ -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{

View file

@ -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 {

View file

@ -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)

View file

@ -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())