Skip to content

Commit

Permalink
feat: implement ext_default_child_storage_storage_kill_version_2 (Cha…
Browse files Browse the repository at this point in the history
…inSafe#1799)

* add code to parse limit param

* add optional encoding for limit

* implement functionity for ext function

* lint

* implement trie.DeleteChildLimit function

* address PR comments

* update HOST_API_TEST_RUNTIME_URL to use master branch

* lint

* combine parameters

* add unit test for DeleteChildLimit

Co-authored-by: noot <36753753+noot@users.noreply.github.com>
  • Loading branch information
edwardmack and noot authored Oct 7, 2021
1 parent cdf6ed8 commit c2908ae
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 6 deletions.
2 changes: 2 additions & 0 deletions lib/runtime/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package runtime
import (
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/common/optional"
"github.com/ChainSafe/gossamer/lib/keystore"
"github.com/ChainSafe/gossamer/lib/transaction"
"github.com/ChainSafe/gossamer/lib/trie"
Expand Down Expand Up @@ -66,6 +67,7 @@ type Storage interface {
GetChildStorage(keyToChild, key []byte) ([]byte, error)
Delete(key []byte)
DeleteChild(keyToChild []byte)
DeleteChildLimit(keyToChild []byte, limit *optional.Bytes) (uint32, bool, error)
ClearChildStorage(keyToChild, key []byte) error
NextKey([]byte) []byte
ClearPrefixInChild(keyToChild, prefix []byte) error
Expand Down
39 changes: 39 additions & 0 deletions lib/runtime/storage/trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package storage

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

"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/common/optional"
"github.com/ChainSafe/gossamer/lib/trie"
)

Expand Down Expand Up @@ -178,6 +180,43 @@ func (s *TrieState) DeleteChild(key []byte) {
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 *optional.Bytes) (uint32, bool, 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 || !limit.Exists() {
s.t.DeleteChild(key)
return qtyEntries, true, nil
}
limitUint := binary.LittleEndian.Uint32(limit.Value())

keys := make([]string, 0, len(tr.Entries()))
for k := range tr.Entries() {
keys = append(keys, k)
}
sort.Strings(keys)
deleted := uint32(0)
for _, k := range keys {
tr.Delete([]byte(k))
deleted++
if deleted == limitUint {
break
}
}

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

return deleted, false, nil
}

// ClearChildStorage removes the child storage entry from the trie
func (s *TrieState) ClearChildStorage(keyToChild, key []byte) error {
s.lock.Lock()
Expand Down
49 changes: 49 additions & 0 deletions lib/runtime/storage/trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package storage

import (
"bytes"
"encoding/binary"
"sort"
"testing"

"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/common/optional"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -197,3 +199,50 @@ func TestTrieState_RollbackStorageTransaction(t *testing.T) {
val := ts.Get([]byte(testCases[0]))
require.Equal(t, []byte(testCases[0]), val)
}

func TestTrieState_DeleteChildLimit(t *testing.T) {
ts := newTestTrieState(t)
child := trie.NewEmptyTrie()

keys := []string{
"key3",
"key1",
"key2",
}

for i, key := range keys {
child.Put([]byte(key), []byte{byte(i)})
}

keyToChild := []byte("keytochild")

err := ts.SetChild(keyToChild, child)
require.NoError(t, err)

testLimitBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(testLimitBytes, uint32(2))
optLimit2 := optional.NewBytes(true, testLimitBytes)

testCases := []struct {
key []byte
limit *optional.Bytes
expectedDeleted uint32
expectedDelAll bool
errMsg string
}{
{key: []byte("fakekey"), limit: optLimit2, expectedDeleted: 0, expectedDelAll: false, errMsg: "child trie does not exist at key :child_storage:default:fakekey"},
{key: []byte("keytochild"), limit: optLimit2, expectedDeleted: 2, expectedDelAll: false},
{key: []byte("keytochild"), limit: nil, expectedDeleted: 1, expectedDelAll: true},
}
for _, test := range testCases {
deleted, all, err := ts.DeleteChildLimit(test.key, test.limit)
if test.errMsg != "" {
require.Error(t, err)
require.EqualError(t, err, test.errMsg)
continue
}
require.NoError(t, err)
require.Equal(t, test.expectedDeleted, deleted)
require.Equal(t, test.expectedDelAll, all)
}
}
26 changes: 20 additions & 6 deletions lib/runtime/wasmer/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -1118,19 +1118,33 @@ func ext_default_child_storage_storage_kill_version_1(context unsafe.Pointer, ch
}

//export ext_default_child_storage_storage_kill_version_2
func ext_default_child_storage_storage_kill_version_2(context unsafe.Pointer, childStorageKeySpan, _ C.int64_t) C.int32_t {
func ext_default_child_storage_storage_kill_version_2(context unsafe.Pointer, childStorageKeySpan, lim C.int64_t) C.int32_t {
logger.Debug("[ext_default_child_storage_storage_kill_version_2] executing...")
logger.Warn("[ext_default_child_storage_storage_kill_version_2] somewhat unimplemented")
// TODO: need to use `limit` parameter

instanceContext := wasm.IntoInstanceContext(context)
ctx := instanceContext.Data().(*runtime.Context)
storage := ctx.Storage

childStorageKey := asMemorySlice(instanceContext, childStorageKeySpan)
storage.DeleteChild(childStorageKey)

// note: this function always returns `KillStorageResult::AllRemoved`, which is 0
limitBytes := asMemorySlice(instanceContext, lim)
buf := &bytes.Buffer{}
buf.Write(limitBytes)

limit, err := optional.NewBytes(true, nil).Decode(buf)
if err != nil {
logger.Warn("[ext_default_child_storage_storage_kill_version_2] cannot generate limit", "error", err)
return 0
}

_, all, err := storage.DeleteChildLimit(childStorageKey, limit)
if err != nil {
logger.Warn("[ext_default_child_storage_storage_kill_version_2] cannot get child storage", "error", err)
}

if all {
return 1
}

return 0
}

Expand Down
96 changes: 96 additions & 0 deletions lib/runtime/wasmer/imports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package wasmer

import (
"bytes"
"encoding/binary"
"os"
"sort"
"testing"
Expand Down Expand Up @@ -1071,6 +1072,101 @@ func Test_ext_default_child_storage_storage_kill_version_1(t *testing.T) {
require.Nil(t, child)
}

func Test_ext_default_child_storage_storage_kill_version_2_limit_all(t *testing.T) {
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)

tr := trie.NewEmptyTrie()
tr.Put([]byte(`key2`), []byte(`value2`))
tr.Put([]byte(`key1`), []byte(`value1`))
err := inst.ctx.Storage.SetChild(testChildKey, tr)
require.NoError(t, err)

// Confirm if value is set
child, err := inst.ctx.Storage.GetChild(testChildKey)
require.NoError(t, err)
require.NotNil(t, child)

encChildKey, err := scale.Encode(testChildKey)
require.NoError(t, err)

testLimit := uint32(2)
testLimitBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(testLimitBytes, testLimit)

optLimit, err := optional.NewBytes(true, testLimitBytes).Encode()
require.NoError(t, err)

res, err := inst.Exec("rtm_ext_default_child_storage_storage_kill_version_2", append(encChildKey, optLimit...))
require.NoError(t, err)
require.Equal(t, []byte{1, 0, 0, 0}, res)

child, err = inst.ctx.Storage.GetChild(testChildKey)
require.NoError(t, err)
require.Equal(t, 0, len(child.Entries()))
}

func Test_ext_default_child_storage_storage_kill_version_2_limit_1(t *testing.T) {
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)

tr := trie.NewEmptyTrie()
tr.Put([]byte(`key2`), []byte(`value2`))
tr.Put([]byte(`key1`), []byte(`value1`))
err := inst.ctx.Storage.SetChild(testChildKey, tr)
require.NoError(t, err)

// Confirm if value is set
child, err := inst.ctx.Storage.GetChild(testChildKey)
require.NoError(t, err)
require.NotNil(t, child)

encChildKey, err := scale.Encode(testChildKey)
require.NoError(t, err)

testLimit := uint32(1)
testLimitBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(testLimitBytes, testLimit)

optLimit, err := optional.NewBytes(true, testLimitBytes).Encode()
require.NoError(t, err)

res, err := inst.Exec("rtm_ext_default_child_storage_storage_kill_version_2", append(encChildKey, optLimit...))
require.NoError(t, err)
require.Equal(t, []byte{0, 0, 0, 0}, res)

child, err = inst.ctx.Storage.GetChild(testChildKey)
require.NoError(t, err)
require.Equal(t, 1, len(child.Entries()))
}

func Test_ext_default_child_storage_storage_kill_version_2_limit_none(t *testing.T) {
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)

tr := trie.NewEmptyTrie()
tr.Put([]byte(`key2`), []byte(`value2`))
tr.Put([]byte(`key1`), []byte(`value1`))
err := inst.ctx.Storage.SetChild(testChildKey, tr)
require.NoError(t, err)

// Confirm if value is set
child, err := inst.ctx.Storage.GetChild(testChildKey)
require.NoError(t, err)
require.NotNil(t, child)

encChildKey, err := scale.Encode(testChildKey)
require.NoError(t, err)

optLimit, err := optional.NewBytes(false, nil).Encode()
require.NoError(t, err)

res, err := inst.Exec("rtm_ext_default_child_storage_storage_kill_version_2", append(encChildKey, optLimit...))
require.NoError(t, err)
require.Equal(t, []byte{1, 0, 0, 0}, res)

child, err = inst.ctx.Storage.GetChild(testChildKey)
require.Error(t, err)
require.Nil(t, child)
}

func Test_ext_storage_append_version_1(t *testing.T) {
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)

Expand Down

0 comments on commit c2908ae

Please sign in to comment.