diff --git a/dot/services.go b/dot/services.go index 76c2b8e707..853cc275c2 100644 --- a/dot/services.go +++ b/dot/services.go @@ -21,6 +21,8 @@ import ( "fmt" "math/big" + database "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/rpc" @@ -90,12 +92,17 @@ func createRuntime(cfg *Config, st *state.Service, ks *keystore.GenericKeystore) return nil, err } + ns := runtime.NodeStorage{ + LocalStorage: database.NewMemDatabase(), + PersistentStorage: database.NewTable(st.DB(), "offlinestorage"), + } rtCfg := &runtime.Config{ - Storage: ts, - Keystore: ks, - Imports: runtime.RegisterImports_NodeRuntime, - LogLvl: lvl, - Role: cfg.Core.Roles, + Storage: ts, + Keystore: ks, + Imports: runtime.RegisterImports_NodeRuntime, + LogLvl: lvl, + NodeStorage: ns, + Role: cfg.Core.Roles, } // create runtime executor diff --git a/lib/runtime/imports_old.go b/lib/runtime/imports_old.go index cf070c362a..d9c9a51e24 100644 --- a/lib/runtime/imports_old.go +++ b/lib/runtime/imports_old.go @@ -728,8 +728,32 @@ func ext_is_validator(context unsafe.Pointer) int32 { //export ext_local_storage_get func ext_local_storage_get(context unsafe.Pointer, kind, key, keyLen, valueLen int32) int32 { logger.Trace("[ext_local_storage_get] executing...") - logger.Warn("[ext_local_storage_get] Not yet implemented.") - return 0 + instanceContext := wasm.IntoInstanceContext(context) + memory := instanceContext.Memory().Data() + + keyM := memory[key : key+keyLen] + runtimeCtx := instanceContext.Data().(*Ctx) + var res []byte + var err error + switch kind { + case NodeStorageTypePersistent: + res, err = runtimeCtx.nodeStorage.PersistentStorage.Get(keyM) + case NodeStorageTypeLocal: + res, err = runtimeCtx.nodeStorage.LocalStorage.Get(keyM) + } + + if err != nil { + logger.Error("[ext_local_storage_get]", "error", err) + return 0 + } + // allocate memory for value and copy value to memory + ptr, err := runtimeCtx.allocator.Allocate(uint32(valueLen)) + if err != nil { + logger.Error("[ext_local_storage_get]", "error", err) + return 0 + } + copy(memory[ptr:ptr+uint32(valueLen)], res[:]) + return int32(ptr) } //export ext_local_storage_compare_and_set @@ -756,7 +780,24 @@ func ext_submit_transaction(context unsafe.Pointer, data, len int32) int32 { //export ext_local_storage_set func ext_local_storage_set(context unsafe.Pointer, kind, key, keyLen, value, valueLen int32) { logger.Trace("[ext_local_storage_set] executing...") - logger.Warn("[ext_local_storage_set] Not yet implemented.") + instanceContext := wasm.IntoInstanceContext(context) + memory := instanceContext.Memory().Data() + + keyM := memory[key : key+keyLen] + valueM := memory[value : value+valueLen] + + runtimeCtx := instanceContext.Data().(*Ctx) + + var err error + switch kind { + case NodeStorageTypePersistent: + err = runtimeCtx.nodeStorage.PersistentStorage.Put(keyM, valueM) + case NodeStorageTypeLocal: + err = runtimeCtx.nodeStorage.LocalStorage.Put(keyM, valueM) + } + if err != nil { + logger.Error("[ext_local_storage_set]", "error", err) + } } // RegisterImports_TestRuntime registers the wasm imports for the v0.6.x substrate test runtime diff --git a/lib/runtime/imports_old_test.go b/lib/runtime/imports_old_test.go index a993d22399..84ff66bdae 100644 --- a/lib/runtime/imports_old_test.go +++ b/lib/runtime/imports_old_test.go @@ -1132,6 +1132,118 @@ func TestExt_set_child_storage(t *testing.T) { } } +func TestExt_local_storage_set_local(t *testing.T) { + runtime := NewTestRuntime(t, TEST_RUNTIME) + + mem := runtime.vm.Memory.Data() + + key := []byte("mykey") + value := []byte("myvalue") + + keyPtr := 0 + keyLen := len(key) + valuePtr := keyPtr + keyLen + valueLen := len(value) + + copy(mem[keyPtr:keyPtr+keyLen], key) + copy(mem[valuePtr:valuePtr+valueLen], value) + + // call wasm function + testFunc, ok := runtime.vm.Exports["test_ext_local_storage_set"] + if !ok { + t.Fatal("could not find exported function") + } + + _, err := testFunc(NodeStorageTypeLocal, keyPtr, keyLen, valuePtr, valueLen) + require.NoError(t, err) + + resValue, err := runtime.ctx.nodeStorage.LocalStorage.Get(key) + require.NoError(t, err) + require.Equal(t, value, resValue) +} + +func TestExt_local_storage_set_persistent(t *testing.T) { + runtime := NewTestRuntime(t, TEST_RUNTIME) + + mem := runtime.vm.Memory.Data() + + key := []byte("mykey") + value := []byte("myvalue") + + keyPtr := 0 + keyLen := len(key) + valuePtr := keyPtr + keyLen + valueLen := len(value) + + copy(mem[keyPtr:keyPtr+keyLen], key) + copy(mem[valuePtr:valuePtr+valueLen], value) + + // call wasm function + testFunc, ok := runtime.vm.Exports["test_ext_local_storage_set"] + if !ok { + t.Fatal("could not find exported function") + } + + _, err := testFunc(NodeStorageTypePersistent, keyPtr, keyLen, valuePtr, valueLen) + require.NoError(t, err) + + resValue, err := runtime.ctx.nodeStorage.PersistentStorage.Get(key) + require.NoError(t, err) + require.Equal(t, value, resValue) +} + +func TestExt_local_storage_get_local(t *testing.T) { + runtime := NewTestRuntime(t, TEST_RUNTIME) + mem := runtime.vm.Memory.Data() + + key := []byte("mykey") + value := []byte("myvalue") + runtime.ctx.nodeStorage.LocalStorage.Put(key, value) + + keyPtr := 0 + keyLen := len(key) + valueLen := len(value) + + copy(mem[keyPtr:keyPtr+keyLen], key) + + // call wasm function + testFunc, ok := runtime.vm.Exports["test_ext_local_storage_get"] + if !ok { + t.Fatal("could not find exported function") + } + + res, err := testFunc(NodeStorageTypeLocal, keyPtr, keyLen, valueLen) + require.Nil(t, err) + + require.Equal(t, value, mem[res.ToI32():res.ToI32()+int32(valueLen)]) +} + +func TestExt_local_storage_get_persistent(t *testing.T) { + runtime := NewTestRuntime(t, TEST_RUNTIME) + mem := runtime.vm.Memory.Data() + + key := []byte("mykey") + value := []byte("myvalue") + runtime.ctx.nodeStorage.PersistentStorage.Put(key, value) + + keyPtr := 0 + keyLen := len(key) + valueLen := len(value) + + copy(mem[keyPtr:keyPtr+keyLen], key) + + // call wasm function + testFunc, ok := runtime.vm.Exports["test_ext_local_storage_get"] + if !ok { + t.Fatal("could not find exported function") + } + + res, err := testFunc(NodeStorageTypePersistent, keyPtr, keyLen, valueLen) + require.Nil(t, err) + + require.Equal(t, value, mem[res.ToI32():res.ToI32()+int32(valueLen)]) +} + func TestExt_is_validator(t *testing.T) { // test with validator runtime := NewTestRuntimeWithRole(t, TEST_RUNTIME, byte(4)) @@ -1154,5 +1266,4 @@ func TestExt_is_validator(t *testing.T) { res, err = testFunc() require.NoError(t, err) require.Equal(t, int32(0), res.ToI32()) - } diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index 8e52b2235b..fa3ea89f34 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -34,3 +34,9 @@ type Storage interface { SetBalance(key [32]byte, balance uint64) error GetBalance(key [32]byte) (uint64, error) } + +// BasicStorage interface for functions used by runtime offchain workers +type BasicStorage interface { + Put(key []byte, value []byte) error + Get(key []byte) ([]byte, error) +} diff --git a/lib/runtime/runtime.go b/lib/runtime/runtime.go index f7f89c70f4..f8e193741c 100644 --- a/lib/runtime/runtime.go +++ b/lib/runtime/runtime.go @@ -29,21 +29,35 @@ import ( var memory, memErr = wasm.NewMemory(17, 0) var logger = log.New("pkg", "runtime") +// NodeStorageTypePersistent flag to identify offchain storage as persistent (db) +const NodeStorageTypePersistent int32 = 1 + +// NodeStorageTypeLocal flog to identify offchain storage as local (memory) +const NodeStorageTypeLocal int32 = 2 + +// NodeStorage struct for storage of runtime offchain worker data +type NodeStorage struct { + LocalStorage BasicStorage + PersistentStorage BasicStorage +} + // Ctx struct type Ctx struct { - storage Storage - allocator *FreeingBumpHeapAllocator - keystore *keystore.GenericKeystore - validator bool + storage Storage + allocator *FreeingBumpHeapAllocator + keystore *keystore.GenericKeystore + nodeStorage NodeStorage + validator bool } // Config represents a runtime configuration type Config struct { - Storage Storage - Keystore *keystore.GenericKeystore - Imports func() (*wasm.Imports, error) - LogLvl log.Lvl - Role byte + Storage Storage + Keystore *keystore.GenericKeystore + Imports func() (*wasm.Imports, error) + LogLvl log.Lvl + NodeStorage NodeStorage + Role byte } // Runtime struct @@ -100,10 +114,11 @@ func NewRuntime(code []byte, cfg *Config) (*Runtime, error) { } runtimeCtx := &Ctx{ - storage: cfg.Storage, - allocator: memAllocator, - keystore: cfg.Keystore, - validator: validator, + storage: cfg.Storage, + allocator: memAllocator, + keystore: cfg.Keystore, + nodeStorage: cfg.NodeStorage, + validator: validator, } logger.Debug("NewRuntime", "runtimeCtx", runtimeCtx) diff --git a/lib/runtime/test_helpers.go b/lib/runtime/test_helpers.go index 595c5f2a3c..ea81238f8b 100644 --- a/lib/runtime/test_helpers.go +++ b/lib/runtime/test_helpers.go @@ -26,6 +26,7 @@ import ( "path/filepath" "testing" + database "github.com/ChainSafe/chaindb" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/keystore" "github.com/ChainSafe/gossamer/lib/trie" @@ -55,11 +56,16 @@ func NewTestRuntimeWithTrie(t *testing.T, targetRuntime string, tt *trie.Trie, l fp, err := filepath.Abs(testRuntimeFilePath) require.Nil(t, err, "could not create testRuntimeFilePath", "targetRuntime", targetRuntime) + ns := NodeStorage{ + LocalStorage: database.NewMemDatabase(), + PersistentStorage: database.NewMemDatabase(), // we're using a local storage here since this is a test runtime + } cfg := &Config{ - Storage: s, - Keystore: keystore.NewGenericKeystore("test"), - Imports: importsFunc, - LogLvl: lvl, + Storage: s, + Keystore: keystore.NewGenericKeystore("test"), + Imports: importsFunc, + LogLvl: lvl, + NodeStorage: ns, } r, err := NewRuntimeFromFile(fp, cfg)