mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-30 09:33:36 +00:00
mpt: allow to remove non-existent keys in batch
This bug was here before batch were intoduced. `Delete` is allowed to be called on missing keys with HALT result, MPT needs to take this into account.
This commit is contained in:
parent
4d0681d898
commit
0cb6ec7345
6 changed files with 37 additions and 15 deletions
|
@ -1591,3 +1591,15 @@ func TestInvalidNotification(t *testing.T) {
|
||||||
require.Nil(t, aer.Stack[0])
|
require.Nil(t, aer.Stack[0])
|
||||||
require.Equal(t, stackitem.InteropT, aer.Stack[1].Type())
|
require.Equal(t, stackitem.InteropT, aer.Stack[1].Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that deletion of non-existent doesn't result in error in tx or block addition.
|
||||||
|
func TestMPTDeleteNoKey(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
cs, _ := getTestContractState(bc)
|
||||||
|
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
|
||||||
|
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, vm.HaltState, aer.VMState)
|
||||||
|
}
|
||||||
|
|
|
@ -342,6 +342,10 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
|
delValOff := w.Len()
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
||||||
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete)
|
||||||
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
||||||
onNEP17PaymentOff := w.Len()
|
onNEP17PaymentOff := w.Len()
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
|
||||||
emit.Int(w.BinWriter, 4)
|
emit.Int(w.BinWriter, 4)
|
||||||
|
@ -464,6 +468,14 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
|
||||||
},
|
},
|
||||||
ReturnType: smartcontract.VoidType,
|
ReturnType: smartcontract.VoidType,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "delValue",
|
||||||
|
Offset: delValOff,
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("key", smartcontract.StringType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: manifest.MethodOnNEP11Payment,
|
Name: manifest.MethodOnNEP11Payment,
|
||||||
Offset: onNEP11PaymentOff,
|
Offset: onNEP11PaymentOff,
|
||||||
|
|
|
@ -207,10 +207,8 @@ func (t *Trie) newSubTrieMany(prefix []byte, kv []keyValue, value []byte) (Node,
|
||||||
if len(kv[0].key) == 0 {
|
if len(kv[0].key) == 0 {
|
||||||
if len(kv[0].value) == 0 {
|
if len(kv[0].value) == 0 {
|
||||||
if len(kv) == 1 {
|
if len(kv) == 1 {
|
||||||
if len(value) != 0 {
|
|
||||||
return new(HashNode), 1, nil
|
return new(HashNode), 1, nil
|
||||||
}
|
|
||||||
return new(HashNode), 0, ErrNotFound
|
|
||||||
}
|
}
|
||||||
node, n, err := t.newSubTrieMany(prefix, kv[1:], nil)
|
node, n, err := t.newSubTrieMany(prefix, kv[1:], nil)
|
||||||
return node, n + 1, err
|
return node, n + 1, err
|
||||||
|
|
|
@ -170,7 +170,7 @@ func TestTrie_PutBatchBranch(t *testing.T) {
|
||||||
{[]byte{0x20, 2}, nil},
|
{[]byte{0x20, 2}, nil},
|
||||||
{[]byte{0x30, 3}, []byte("won't be put")},
|
{[]byte{0x30, 3}, []byte("won't be put")},
|
||||||
}
|
}
|
||||||
testIncompletePut(t, ps, 1, tr1, tr2)
|
testIncompletePut(t, ps, 3, tr1, tr2)
|
||||||
})
|
})
|
||||||
t.Run("incomplete put, transform to empty", func(t *testing.T) {
|
t.Run("incomplete put, transform to empty", func(t *testing.T) {
|
||||||
tr1, tr2 := prepareBranch(t)
|
tr1, tr2 := prepareBranch(t)
|
||||||
|
@ -180,7 +180,7 @@ func TestTrie_PutBatchBranch(t *testing.T) {
|
||||||
{[]byte{0x20, 2}, nil},
|
{[]byte{0x20, 2}, nil},
|
||||||
{[]byte{0x30, 3}, []byte("won't be put")},
|
{[]byte{0x30, 3}, []byte("won't be put")},
|
||||||
}
|
}
|
||||||
testIncompletePut(t, ps, 2, tr1, tr2)
|
testIncompletePut(t, ps, 4, tr1, tr2)
|
||||||
})
|
})
|
||||||
t.Run("remove 2, become empty", func(t *testing.T) {
|
t.Run("remove 2, become empty", func(t *testing.T) {
|
||||||
tr1, tr2 := prepareBranch(t)
|
tr1, tr2 := prepareBranch(t)
|
||||||
|
@ -247,7 +247,7 @@ func TestTrie_PutBatchEmpty(t *testing.T) {
|
||||||
}
|
}
|
||||||
tr1 := NewTrie(new(HashNode), false, newTestStore())
|
tr1 := NewTrie(new(HashNode), false, newTestStore())
|
||||||
tr2 := NewTrie(new(HashNode), false, newTestStore())
|
tr2 := NewTrie(new(HashNode), false, newTestStore())
|
||||||
testIncompletePut(t, ps, 2, tr1, tr2)
|
testIncompletePut(t, ps, 4, tr1, tr2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -290,7 +290,7 @@ func (t *Trie) deleteFromBranch(b *BranchNode, path []byte) (Node, error) {
|
||||||
|
|
||||||
func (t *Trie) deleteFromExtension(n *ExtensionNode, path []byte) (Node, error) {
|
func (t *Trie) deleteFromExtension(n *ExtensionNode, path []byte) (Node, error) {
|
||||||
if !bytes.HasPrefix(path, n.key) {
|
if !bytes.HasPrefix(path, n.key) {
|
||||||
return nil, ErrNotFound
|
return n, nil
|
||||||
}
|
}
|
||||||
h := n.Hash()
|
h := n.Hash()
|
||||||
bs := n.bytes
|
bs := n.bytes
|
||||||
|
@ -325,14 +325,14 @@ func (t *Trie) deleteFromNode(curr Node, path []byte) (Node, error) {
|
||||||
t.removeRef(curr.Hash(), curr.Bytes())
|
t.removeRef(curr.Hash(), curr.Bytes())
|
||||||
return new(HashNode), nil
|
return new(HashNode), nil
|
||||||
}
|
}
|
||||||
return nil, ErrNotFound
|
return curr, nil
|
||||||
case *BranchNode:
|
case *BranchNode:
|
||||||
return t.deleteFromBranch(n, path)
|
return t.deleteFromBranch(n, path)
|
||||||
case *ExtensionNode:
|
case *ExtensionNode:
|
||||||
return t.deleteFromExtension(n, path)
|
return t.deleteFromExtension(n, path)
|
||||||
case *HashNode:
|
case *HashNode:
|
||||||
if n.IsEmpty() {
|
if n.IsEmpty() {
|
||||||
return nil, ErrNotFound
|
return n, nil
|
||||||
}
|
}
|
||||||
newNode, err := t.getFromStore(n.Hash())
|
newNode, err := t.getFromStore(n.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -62,8 +62,8 @@ func testTrieRefcount(t *testing.T, key1, key2 []byte) {
|
||||||
tr.testHas(t, key1, nil)
|
tr.testHas(t, key1, nil)
|
||||||
tr.testHas(t, key2, []byte{1})
|
tr.testHas(t, key2, []byte{1})
|
||||||
|
|
||||||
// error on delete, refcount should not be updated
|
// delete non-existent, refcount should not be updated
|
||||||
require.Error(t, tr.Delete(key1))
|
require.NoError(t, tr.Delete(key1))
|
||||||
tr.Flush()
|
tr.Flush()
|
||||||
tr.testHas(t, key1, nil)
|
tr.testHas(t, key1, nil)
|
||||||
tr.testHas(t, key2, []byte{1})
|
tr.testHas(t, key2, []byte{1})
|
||||||
|
@ -316,7 +316,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
||||||
|
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
tr := NewTrie(nil, enableGC, newTestStore())
|
tr := NewTrie(nil, enableGC, newTestStore())
|
||||||
require.Error(t, tr.Delete([]byte{}))
|
require.NoError(t, tr.Delete([]byte{}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
||||||
l := NewLeafNode([]byte{0x12, 0x34})
|
l := NewLeafNode([]byte{0x12, 0x34})
|
||||||
tr := NewTrie(l, enableGC, newTestStore())
|
tr := NewTrie(l, enableGC, newTestStore())
|
||||||
t.Run("NonExistentKey", func(t *testing.T) {
|
t.Run("NonExistentKey", func(t *testing.T) {
|
||||||
require.Error(t, tr.Delete([]byte{0x12}))
|
require.NoError(t, tr.Delete([]byte{0x12}))
|
||||||
tr.testHas(t, []byte{}, []byte{0x12, 0x34})
|
tr.testHas(t, []byte{}, []byte{0x12, 0x34})
|
||||||
})
|
})
|
||||||
require.NoError(t, tr.Delete([]byte{}))
|
require.NoError(t, tr.Delete([]byte{}))
|
||||||
|
@ -338,7 +338,7 @@ func testTrieDelete(t *testing.T, enableGC bool) {
|
||||||
tr := NewTrie(e, enableGC, newTestStore())
|
tr := NewTrie(e, enableGC, newTestStore())
|
||||||
|
|
||||||
t.Run("NonExistentKey", func(t *testing.T) {
|
t.Run("NonExistentKey", func(t *testing.T) {
|
||||||
require.Error(t, tr.Delete([]byte{}))
|
require.NoError(t, tr.Delete([]byte{}))
|
||||||
tr.testHas(t, []byte{0xAB}, []byte{0x12, 0x34})
|
tr.testHas(t, []byte{0xAB}, []byte{0x12, 0x34})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue