Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lib/trie): record deleted Merkle values fixed #2873

Merged
merged 14 commits into from
Jan 10, 2023
3 changes: 2 additions & 1 deletion dot/state/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ func generateBlockWithRandomTrie(t *testing.T, serv *Service,
rand := time.Now().UnixNano()
key := []byte("testKey" + fmt.Sprint(rand))
value := []byte("testValue" + fmt.Sprint(rand))
trieState.Put(key, value)
err = trieState.Put(key, value)
require.NoError(t, err)

trieStateRoot, err := trieState.Root()
require.NoError(t, err)
Expand Down
14 changes: 8 additions & 6 deletions lib/runtime/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ import (

// Storage runtime interface.
type Storage interface {
Put(key []byte, value []byte)
Put(key []byte, value []byte) (err error)
Get(key []byte) []byte
Root() (common.Hash, error)
SetChild(keyToChild []byte, child *trie.Trie) error
SetChildStorage(keyToChild, key, value []byte) error
GetChildStorage(keyToChild, key []byte) ([]byte, error)
Delete(key []byte)
DeleteChild(keyToChild []byte)
DeleteChildLimit(keyToChild []byte, limit *[]byte) (uint32, bool, error)
Delete(key []byte) (err error)
DeleteChild(keyToChild []byte) (err error)
DeleteChildLimit(keyToChild []byte, limit *[]byte) (
deleted uint32, allDeleted bool, err error)
ClearChildStorage(keyToChild, key []byte) error
NextKey([]byte) []byte
ClearPrefixInChild(keyToChild, prefix []byte) error
GetChildNextKey(keyToChild, key []byte) ([]byte, error)
GetChild(keyToChild []byte) (*trie.Trie, error)
ClearPrefix(prefix []byte)
ClearPrefixLimit(prefix []byte, limit uint32) (uint32, bool)
ClearPrefix(prefix []byte) (err error)
ClearPrefixLimit(prefix []byte, limit uint32) (
deleted uint32, allDeleted bool, err error)
BeginStorageTransaction()
CommitStorageTransaction()
RollbackStorageTransaction()
Expand Down
70 changes: 45 additions & 25 deletions lib/runtime/storage/trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package storage

import (
"encoding/binary"
"fmt"
"sort"
"sync"

Expand Down Expand Up @@ -67,11 +68,11 @@ func (s *TrieState) RollbackStorageTransaction() {
s.oldTrie = nil
}

// Put puts thread safely a value at the specified key in the trie.
func (s *TrieState) Put(key, value []byte) {
// Put puts a key-value pair in the trie
func (s *TrieState) Put(key, value []byte) (err error) {
s.lock.Lock()
defer s.lock.Unlock()
s.t.Put(key, value)
return s.t.Put(key, value)
}

// Get gets a value from the trie
Expand All @@ -97,15 +98,20 @@ func (s *TrieState) Has(key []byte) bool {
}

// Delete deletes a key from the trie
func (s *TrieState) Delete(key []byte) {
func (s *TrieState) Delete(key []byte) (err error) {
val := s.t.Get(key)
if val == nil {
return
return nil
}

s.lock.Lock()
defer s.lock.Unlock()
s.t.Delete(key)
err = s.t.Delete(key)
if err != nil {
return fmt.Errorf("deleting from trie: %w", err)
}

return nil
}

// NextKey returns the next key in the trie in lexicographical order. If it does not exist, it returns nil.
Expand All @@ -116,19 +122,19 @@ func (s *TrieState) NextKey(key []byte) []byte {
}

// ClearPrefix deletes all key-value pairs from the trie where the key starts with the given prefix
func (s *TrieState) ClearPrefix(prefix []byte) {
func (s *TrieState) ClearPrefix(prefix []byte) (err error) {
s.lock.Lock()
defer s.lock.Unlock()
s.t.ClearPrefix(prefix)
return s.t.ClearPrefix(prefix)
}

// ClearPrefixLimit deletes key-value pairs from the trie where the key starts with the given prefix till limit reached
func (s *TrieState) ClearPrefixLimit(prefix []byte, limit uint32) (uint32, bool) {
func (s *TrieState) ClearPrefixLimit(prefix []byte, limit uint32) (
deleted uint32, allDeleted bool, err error) {
s.lock.Lock()
defer s.lock.Unlock()

num, del := s.t.ClearPrefixLimit(prefix, limit)
return num, del
return s.t.ClearPrefixLimit(prefix, limit)
}

// TrieEntries returns every key-value pair in the trie
Expand Down Expand Up @@ -167,24 +173,29 @@ func (s *TrieState) GetChildStorage(keyToChild, key []byte) ([]byte, error) {
}

// DeleteChild deletes a child trie from the main trie
func (s *TrieState) DeleteChild(key []byte) {
func (s *TrieState) DeleteChild(key []byte) (err error) {
s.lock.Lock()
defer s.lock.Unlock()
s.t.DeleteChild(key)
return s.t.DeleteChild(key)
}

// DeleteChildLimit deletes up to limit of database entries by lexicographic order, return number
// deleted, true if all delete otherwise false
func (s *TrieState) DeleteChildLimit(key []byte, limit *[]byte) (uint32, bool, error) {
// DeleteChildLimit deletes up to limit of database entries by lexicographic order.
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
func (s *TrieState) DeleteChildLimit(key []byte, limit *[]byte) (
deleted uint32, allDeleted bool, err error) {
s.lock.Lock()
defer s.lock.Unlock()
tr, err := s.t.GetChild(key)
if err != nil {
return 0, false, err
}

qtyEntries := uint32(len(tr.Entries()))
if limit == nil {
s.t.DeleteChild(key)
err = s.t.DeleteChild(key)
if err != nil {
return 0, false, fmt.Errorf("deleting child trie: %w", err)
}

return qtyEntries, true, nil
}
limitUint := binary.LittleEndian.Uint32(*limit)
Expand All @@ -194,20 +205,25 @@ func (s *TrieState) DeleteChildLimit(key []byte, limit *[]byte) (uint32, bool, e
keys = append(keys, k)
}
sort.Strings(keys)
deleted := uint32(0)
for _, k := range keys {
tr.Delete([]byte(k))
// TODO have a transactional/atomic way to delete multiple keys in trie.
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
// If one deletion fails, the child trie and its parent trie are then in
// a bad intermediary state. Take also care of the caching of deleted Merkle
// values within the tries, which is used for online pruning.
// See https://github.com/ChainSafe/gossamer/issues/3032
err = tr.Delete([]byte(k))
if err != nil {
return deleted, allDeleted, fmt.Errorf("deleting from child trie located at key 0x%x: %w", key, err)
}

deleted++
if deleted == limitUint {
break
}
}

if deleted == qtyEntries {
return deleted, true, nil
}

return deleted, false, nil
allDeleted = deleted == qtyEntries
return deleted, allDeleted, nil
}

// ClearChildStorage removes the child storage entry from the trie
Expand All @@ -230,7 +246,11 @@ func (s *TrieState) ClearPrefixInChild(keyToChild, prefix []byte) error {
return nil
}

child.ClearPrefix(prefix)
err = child.ClearPrefix(prefix)
if err != nil {
return fmt.Errorf("clearing prefix in child trie located at key 0x%x: %w", keyToChild, err)
}

return nil
}

Expand Down
15 changes: 12 additions & 3 deletions lib/runtime/wasmer/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func toWasmMemoryFixedSizeOptional(context wasmer.InstanceContext, data []byte)
return toWasmMemory(context, encodedOptionalFixedSize)
}

func storageAppend(storage GetSetter, key, valueToAppend []byte) error {
func storageAppend(storage GetSetter, key, valueToAppend []byte) (err error) {
// this function assumes the item in storage is a SCALE encoded array of items
// the valueToAppend is a new item, so it appends the item and increases the length prefix by 1
currentValue := storage.Get(key)
Expand Down Expand Up @@ -259,7 +259,16 @@ func storageAppend(storage GetSetter, key, valueToAppend []byte) error {
}
}

logger.Debugf("resulting value: 0x%x", value)
storage.Put(key, value)
err = storage.Put(key, value)
if err != nil {
return fmt.Errorf("putting key and value in storage: %w", err)
}

return nil
}

func panicOnError(err error) {
if err != nil {
panic(err)
}
}
11 changes: 11 additions & 0 deletions lib/runtime/wasmer/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package wasmer

import (
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -66,3 +67,13 @@ func Test_pointerSize(t *testing.T) {
})
}
}

func Test_panicOnError(t *testing.T) {
t.Parallel()

err := (error)(nil)
assert.NotPanics(t, func() { panicOnError(err) })

err = errors.New("test error")
assert.PanicsWithValue(t, err, func() { panicOnError(err) })
}
33 changes: 26 additions & 7 deletions lib/runtime/wasmer/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,12 @@ func ext_trie_blake2_256_root_version_1(context unsafe.Pointer, dataSpan C.int64
}

for _, kv := range kvs {
t.Put(kv.Key, kv.Value)
err := t.Put(kv.Key, kv.Value)
if err != nil {
logger.Errorf("failed putting key 0x%x and value 0x%x into trie: %s",
kv.Key, kv.Value, err)
return 0
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// allocate memory for value and copy value to memory
Expand Down Expand Up @@ -865,7 +870,12 @@ func ext_trie_blake2_256_ordered_root_version_1(context unsafe.Pointer, dataSpan
"put key=0x%x and value=0x%x",
key, value)

t.Put(key, value)
err = t.Put(key, value)
if err != nil {
logger.Errorf("failed putting key 0x%x and value 0x%x into trie: %s",
key, value, err)
return 0
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// allocate memory for value and copy value to memory
Expand Down Expand Up @@ -1180,7 +1190,8 @@ func ext_default_child_storage_storage_kill_version_1(context unsafe.Pointer, ch
storage := ctx.Storage

childStorageKey := asMemorySlice(instanceContext, childStorageKeySpan)
storage.DeleteChild(childStorageKey)
err := storage.DeleteChild(childStorageKey)
panicOnError(err)
qdm12 marked this conversation as resolved.
Show resolved Hide resolved
}

//export ext_default_child_storage_storage_kill_version_2
Expand Down Expand Up @@ -1843,7 +1854,8 @@ func ext_storage_clear_version_1(context unsafe.Pointer, keySpan C.int64_t) {
key := asMemorySlice(instanceContext, keySpan)

logger.Debugf("key: 0x%x", key)
storage.Delete(key)
err := storage.Delete(key)
panicOnError(err)
}

//export ext_storage_clear_prefix_version_1
Expand All @@ -1856,7 +1868,8 @@ func ext_storage_clear_prefix_version_1(context unsafe.Pointer, prefixSpan C.int
prefix := asMemorySlice(instanceContext, prefixSpan)
logger.Debugf("prefix: 0x%x", prefix)

storage.ClearPrefix(prefix)
err := storage.ClearPrefix(prefix)
panicOnError(err)
}

//export ext_storage_clear_prefix_version_2
Expand Down Expand Up @@ -1885,7 +1898,12 @@ func ext_storage_clear_prefix_version_2(context unsafe.Pointer, prefixSpan, lim
}

limitUint := binary.LittleEndian.Uint32(limit)
numRemoved, all := storage.ClearPrefixLimit(prefix, limitUint)
numRemoved, all, err := storage.ClearPrefixLimit(prefix, limitUint)
if err != nil {
logger.Errorf("failed to clear prefix limit: %s", err)
return mustToWasmMemoryNil(instanceContext)
}

encBytes, err := toKillStorageResultEnum(all, numRemoved)
if err != nil {
logger.Errorf("failed to allocate memory: %s", err)
Expand Down Expand Up @@ -2044,7 +2062,8 @@ func ext_storage_set_version_1(context unsafe.Pointer, keySpan, valueSpan C.int6
logger.Debugf(
"key 0x%x has value 0x%x",
key, value)
storage.Put(key, cp)
err := storage.Put(key, cp)
panicOnError(err)
}

//export ext_storage_start_transaction_version_1
Expand Down
11 changes: 6 additions & 5 deletions lib/runtime/wasmer/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ type Storage interface {
SetChild(keyToChild []byte, child *trie.Trie) error
SetChildStorage(keyToChild, key, value []byte) error
GetChildStorage(keyToChild, key []byte) ([]byte, error)
Delete(key []byte)
DeleteChild(keyToChild []byte)
Delete(key []byte) (err error)
DeleteChild(keyToChild []byte) (err error)
DeleteChildLimit(keyToChild []byte, limit *[]byte) (uint32, bool, error)
ClearChildStorage(keyToChild, key []byte) error
NextKey([]byte) []byte
ClearPrefixInChild(keyToChild, prefix []byte) error
GetChildNextKey(keyToChild, key []byte) ([]byte, error)
GetChild(keyToChild []byte) (*trie.Trie, error)
ClearPrefix(prefix []byte)
ClearPrefixLimit(prefix []byte, limit uint32) (uint32, bool)
ClearPrefix(prefix []byte) (err error)
ClearPrefixLimit(prefix []byte, limit uint32) (
deleted uint32, allDeleted bool, err error)
BeginStorageTransaction()
CommitStorageTransaction()
RollbackStorageTransaction()
Expand All @@ -46,7 +47,7 @@ type Getter interface {

// Putter puts a value for a key.
type Putter interface {
Put(key []byte, value []byte)
Put(key []byte, value []byte) (err error)
}

// BasicNetwork interface for functions used by runtime network state function
Expand Down
Loading