Skip to content

Commit c2908ae

Browse files
edwardmacknoot
andauthored
feat: implement ext_default_child_storage_storage_kill_version_2 (ChainSafe#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 <[email protected]>
1 parent cdf6ed8 commit c2908ae

File tree

5 files changed

+206
-6
lines changed

5 files changed

+206
-6
lines changed

lib/runtime/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package runtime
1919
import (
2020
"github.com/ChainSafe/gossamer/dot/types"
2121
"github.com/ChainSafe/gossamer/lib/common"
22+
"github.com/ChainSafe/gossamer/lib/common/optional"
2223
"github.com/ChainSafe/gossamer/lib/keystore"
2324
"github.com/ChainSafe/gossamer/lib/transaction"
2425
"github.com/ChainSafe/gossamer/lib/trie"
@@ -66,6 +67,7 @@ type Storage interface {
6667
GetChildStorage(keyToChild, key []byte) ([]byte, error)
6768
Delete(key []byte)
6869
DeleteChild(keyToChild []byte)
70+
DeleteChildLimit(keyToChild []byte, limit *optional.Bytes) (uint32, bool, error)
6971
ClearChildStorage(keyToChild, key []byte) error
7072
NextKey([]byte) []byte
7173
ClearPrefixInChild(keyToChild, prefix []byte) error

lib/runtime/storage/trie.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package storage
1818

1919
import (
2020
"encoding/binary"
21+
"sort"
2122
"sync"
2223

2324
"github.com/ChainSafe/gossamer/lib/common"
25+
"github.com/ChainSafe/gossamer/lib/common/optional"
2426
"github.com/ChainSafe/gossamer/lib/trie"
2527
)
2628

@@ -178,6 +180,43 @@ func (s *TrieState) DeleteChild(key []byte) {
178180
s.t.DeleteChild(key)
179181
}
180182

183+
// DeleteChildLimit deletes up to limit of database entries by lexicographic order, return number
184+
// deleted, true if all delete otherwise false
185+
func (s *TrieState) DeleteChildLimit(key []byte, limit *optional.Bytes) (uint32, bool, error) {
186+
s.lock.Lock()
187+
defer s.lock.Unlock()
188+
tr, err := s.t.GetChild(key)
189+
if err != nil {
190+
return 0, false, err
191+
}
192+
qtyEntries := uint32(len(tr.Entries()))
193+
if limit == nil || !limit.Exists() {
194+
s.t.DeleteChild(key)
195+
return qtyEntries, true, nil
196+
}
197+
limitUint := binary.LittleEndian.Uint32(limit.Value())
198+
199+
keys := make([]string, 0, len(tr.Entries()))
200+
for k := range tr.Entries() {
201+
keys = append(keys, k)
202+
}
203+
sort.Strings(keys)
204+
deleted := uint32(0)
205+
for _, k := range keys {
206+
tr.Delete([]byte(k))
207+
deleted++
208+
if deleted == limitUint {
209+
break
210+
}
211+
}
212+
213+
if deleted == qtyEntries {
214+
return deleted, true, nil
215+
}
216+
217+
return deleted, false, nil
218+
}
219+
181220
// ClearChildStorage removes the child storage entry from the trie
182221
func (s *TrieState) ClearChildStorage(keyToChild, key []byte) error {
183222
s.lock.Lock()

lib/runtime/storage/trie_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ package storage
1818

1919
import (
2020
"bytes"
21+
"encoding/binary"
2122
"sort"
2223
"testing"
2324

2425
"github.com/ChainSafe/gossamer/lib/common"
26+
"github.com/ChainSafe/gossamer/lib/common/optional"
2527
"github.com/ChainSafe/gossamer/lib/trie"
2628
"github.com/stretchr/testify/require"
2729
)
@@ -197,3 +199,50 @@ func TestTrieState_RollbackStorageTransaction(t *testing.T) {
197199
val := ts.Get([]byte(testCases[0]))
198200
require.Equal(t, []byte(testCases[0]), val)
199201
}
202+
203+
func TestTrieState_DeleteChildLimit(t *testing.T) {
204+
ts := newTestTrieState(t)
205+
child := trie.NewEmptyTrie()
206+
207+
keys := []string{
208+
"key3",
209+
"key1",
210+
"key2",
211+
}
212+
213+
for i, key := range keys {
214+
child.Put([]byte(key), []byte{byte(i)})
215+
}
216+
217+
keyToChild := []byte("keytochild")
218+
219+
err := ts.SetChild(keyToChild, child)
220+
require.NoError(t, err)
221+
222+
testLimitBytes := make([]byte, 4)
223+
binary.LittleEndian.PutUint32(testLimitBytes, uint32(2))
224+
optLimit2 := optional.NewBytes(true, testLimitBytes)
225+
226+
testCases := []struct {
227+
key []byte
228+
limit *optional.Bytes
229+
expectedDeleted uint32
230+
expectedDelAll bool
231+
errMsg string
232+
}{
233+
{key: []byte("fakekey"), limit: optLimit2, expectedDeleted: 0, expectedDelAll: false, errMsg: "child trie does not exist at key :child_storage:default:fakekey"},
234+
{key: []byte("keytochild"), limit: optLimit2, expectedDeleted: 2, expectedDelAll: false},
235+
{key: []byte("keytochild"), limit: nil, expectedDeleted: 1, expectedDelAll: true},
236+
}
237+
for _, test := range testCases {
238+
deleted, all, err := ts.DeleteChildLimit(test.key, test.limit)
239+
if test.errMsg != "" {
240+
require.Error(t, err)
241+
require.EqualError(t, err, test.errMsg)
242+
continue
243+
}
244+
require.NoError(t, err)
245+
require.Equal(t, test.expectedDeleted, deleted)
246+
require.Equal(t, test.expectedDelAll, all)
247+
}
248+
}

lib/runtime/wasmer/imports.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,19 +1118,33 @@ func ext_default_child_storage_storage_kill_version_1(context unsafe.Pointer, ch
11181118
}
11191119

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

11261124
instanceContext := wasm.IntoInstanceContext(context)
11271125
ctx := instanceContext.Data().(*runtime.Context)
11281126
storage := ctx.Storage
1129-
11301127
childStorageKey := asMemorySlice(instanceContext, childStorageKeySpan)
1131-
storage.DeleteChild(childStorageKey)
11321128

1133-
// note: this function always returns `KillStorageResult::AllRemoved`, which is 0
1129+
limitBytes := asMemorySlice(instanceContext, lim)
1130+
buf := &bytes.Buffer{}
1131+
buf.Write(limitBytes)
1132+
1133+
limit, err := optional.NewBytes(true, nil).Decode(buf)
1134+
if err != nil {
1135+
logger.Warn("[ext_default_child_storage_storage_kill_version_2] cannot generate limit", "error", err)
1136+
return 0
1137+
}
1138+
1139+
_, all, err := storage.DeleteChildLimit(childStorageKey, limit)
1140+
if err != nil {
1141+
logger.Warn("[ext_default_child_storage_storage_kill_version_2] cannot get child storage", "error", err)
1142+
}
1143+
1144+
if all {
1145+
return 1
1146+
}
1147+
11341148
return 0
11351149
}
11361150

lib/runtime/wasmer/imports_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package wasmer
1818

1919
import (
2020
"bytes"
21+
"encoding/binary"
2122
"os"
2223
"sort"
2324
"testing"
@@ -1071,6 +1072,101 @@ func Test_ext_default_child_storage_storage_kill_version_1(t *testing.T) {
10711072
require.Nil(t, child)
10721073
}
10731074

1075+
func Test_ext_default_child_storage_storage_kill_version_2_limit_all(t *testing.T) {
1076+
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)
1077+
1078+
tr := trie.NewEmptyTrie()
1079+
tr.Put([]byte(`key2`), []byte(`value2`))
1080+
tr.Put([]byte(`key1`), []byte(`value1`))
1081+
err := inst.ctx.Storage.SetChild(testChildKey, tr)
1082+
require.NoError(t, err)
1083+
1084+
// Confirm if value is set
1085+
child, err := inst.ctx.Storage.GetChild(testChildKey)
1086+
require.NoError(t, err)
1087+
require.NotNil(t, child)
1088+
1089+
encChildKey, err := scale.Encode(testChildKey)
1090+
require.NoError(t, err)
1091+
1092+
testLimit := uint32(2)
1093+
testLimitBytes := make([]byte, 4)
1094+
binary.LittleEndian.PutUint32(testLimitBytes, testLimit)
1095+
1096+
optLimit, err := optional.NewBytes(true, testLimitBytes).Encode()
1097+
require.NoError(t, err)
1098+
1099+
res, err := inst.Exec("rtm_ext_default_child_storage_storage_kill_version_2", append(encChildKey, optLimit...))
1100+
require.NoError(t, err)
1101+
require.Equal(t, []byte{1, 0, 0, 0}, res)
1102+
1103+
child, err = inst.ctx.Storage.GetChild(testChildKey)
1104+
require.NoError(t, err)
1105+
require.Equal(t, 0, len(child.Entries()))
1106+
}
1107+
1108+
func Test_ext_default_child_storage_storage_kill_version_2_limit_1(t *testing.T) {
1109+
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)
1110+
1111+
tr := trie.NewEmptyTrie()
1112+
tr.Put([]byte(`key2`), []byte(`value2`))
1113+
tr.Put([]byte(`key1`), []byte(`value1`))
1114+
err := inst.ctx.Storage.SetChild(testChildKey, tr)
1115+
require.NoError(t, err)
1116+
1117+
// Confirm if value is set
1118+
child, err := inst.ctx.Storage.GetChild(testChildKey)
1119+
require.NoError(t, err)
1120+
require.NotNil(t, child)
1121+
1122+
encChildKey, err := scale.Encode(testChildKey)
1123+
require.NoError(t, err)
1124+
1125+
testLimit := uint32(1)
1126+
testLimitBytes := make([]byte, 4)
1127+
binary.LittleEndian.PutUint32(testLimitBytes, testLimit)
1128+
1129+
optLimit, err := optional.NewBytes(true, testLimitBytes).Encode()
1130+
require.NoError(t, err)
1131+
1132+
res, err := inst.Exec("rtm_ext_default_child_storage_storage_kill_version_2", append(encChildKey, optLimit...))
1133+
require.NoError(t, err)
1134+
require.Equal(t, []byte{0, 0, 0, 0}, res)
1135+
1136+
child, err = inst.ctx.Storage.GetChild(testChildKey)
1137+
require.NoError(t, err)
1138+
require.Equal(t, 1, len(child.Entries()))
1139+
}
1140+
1141+
func Test_ext_default_child_storage_storage_kill_version_2_limit_none(t *testing.T) {
1142+
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)
1143+
1144+
tr := trie.NewEmptyTrie()
1145+
tr.Put([]byte(`key2`), []byte(`value2`))
1146+
tr.Put([]byte(`key1`), []byte(`value1`))
1147+
err := inst.ctx.Storage.SetChild(testChildKey, tr)
1148+
require.NoError(t, err)
1149+
1150+
// Confirm if value is set
1151+
child, err := inst.ctx.Storage.GetChild(testChildKey)
1152+
require.NoError(t, err)
1153+
require.NotNil(t, child)
1154+
1155+
encChildKey, err := scale.Encode(testChildKey)
1156+
require.NoError(t, err)
1157+
1158+
optLimit, err := optional.NewBytes(false, nil).Encode()
1159+
require.NoError(t, err)
1160+
1161+
res, err := inst.Exec("rtm_ext_default_child_storage_storage_kill_version_2", append(encChildKey, optLimit...))
1162+
require.NoError(t, err)
1163+
require.Equal(t, []byte{1, 0, 0, 0}, res)
1164+
1165+
child, err = inst.ctx.Storage.GetChild(testChildKey)
1166+
require.Error(t, err)
1167+
require.Nil(t, child)
1168+
}
1169+
10741170
func Test_ext_storage_append_version_1(t *testing.T) {
10751171
inst := NewTestInstance(t, runtime.HOST_API_TEST_RUNTIME)
10761172

0 commit comments

Comments
 (0)