Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dot/rpc/modules/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ type RuntimeStorageAPI interface {
SetPersistent(k, v []byte) error
GetLocal(k []byte) ([]byte, error)
GetPersistent(k []byte) ([]byte, error)
ClearLocal(k []byte) error
ClearPersistent(k []byte) error
}

// SyncStateAPI is the interface to interact with sync state.
Expand Down
28 changes: 28 additions & 0 deletions dot/rpc/modules/mocks/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions dot/rpc/modules/offchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const (
offchainLocal = "LOCAL"
)

// OffchainLocalStorageClear represents the request format to remove data from offchain storage
type OffchainLocalStorageClear struct {
Kind string
Key string
}

// OffchainLocalStorageGet represents the request format to retrieve data from offchain storage
type OffchainLocalStorageGet struct {
Kind string
Expand All @@ -40,6 +46,33 @@ func NewOffchainModule(ns RuntimeStorageAPI) *OffchainModule {
}
}

// LocalStorageClear clear offchain local storage under given key and prefix
func (s *OffchainModule) LocalStorageClear(_ *http.Request, req *OffchainLocalStorageClear, _ *StringResponse) error {
var (
key []byte
err error
)

if key, err = common.HexToBytes(req.Key); err != nil {
return err
}

switch req.Kind {
case offchainPersistent:
err = s.nodeStorage.ClearPersistent(key)
case offchainLocal:
err = s.nodeStorage.ClearLocal(key)
default:
return fmt.Errorf("storage kind not found: %s", req.Kind)
}

if err != nil {
return err
}

return nil
}

// LocalStorageGet get offchain local storage under given key and prefix
func (s *OffchainModule) LocalStorageGet(_ *http.Request, req *OffchainLocalStorageGet, res *StringResponse) error {
var (
Expand Down
72 changes: 72 additions & 0 deletions dot/rpc/modules/offchain_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,71 @@ import (
"go.uber.org/mock/gomock"
)

func Test_OffchainModule_LocalStorageClear(t *testing.T) {
t.Run("clear_local_error", func(t *testing.T) {
ctrl := gomock.NewController(t)

runtimeStorage := mocks.NewMockRuntimeStorageAPI(ctrl)
offchainModule := &OffchainModule{
nodeStorage: runtimeStorage,
}

const keyHex = "0x11111111111111"
request := &OffchainLocalStorageClear{
Kind: offchainLocal,
Key: keyHex,
}
errTest := errors.New("test error")
runtimeStorage.EXPECT().ClearLocal(common.MustHexToBytes(keyHex)).
Return(errTest)

err := offchainModule.LocalStorageClear(nil, request, nil)
assert.ErrorIs(t, err, errTest)
})

t.Run("local_kind", func(t *testing.T) {
ctrl := gomock.NewController(t)

runtimeStorage := mocks.NewMockRuntimeStorageAPI(ctrl)
offchainModule := &OffchainModule{
nodeStorage: runtimeStorage,
}

const keyHex = "0x11111111111111"
request := &OffchainLocalStorageClear{
Kind: offchainLocal,
Key: keyHex,
}
runtimeStorage.EXPECT().ClearLocal(common.MustHexToBytes(keyHex)).
Return(nil)
var response StringResponse
err := offchainModule.LocalStorageClear(nil, request, &response)
require.NoError(t, err)
assert.Empty(t, response)
})

t.Run("persistent_kind", func(t *testing.T) {
ctrl := gomock.NewController(t)

runtimeStorage := mocks.NewMockRuntimeStorageAPI(ctrl)
offchainModule := &OffchainModule{
nodeStorage: runtimeStorage,
}

const keyHex = "0x11111111111111"
request := &OffchainLocalStorageClear{
Kind: offchainPersistent,
Key: keyHex,
}
runtimeStorage.EXPECT().ClearPersistent(common.MustHexToBytes(keyHex)).
Return(nil)
var response StringResponse
err := offchainModule.LocalStorageClear(nil, request, &response)
require.NoError(t, err)
assert.Empty(t, response)
})
}

func Test_OffchainModule_LocalStorageGet(t *testing.T) {
t.Run("get_local_error", func(t *testing.T) {
ctrl := gomock.NewController(t)
Expand Down Expand Up @@ -94,11 +159,18 @@ func TestOffchainStorage_OtherKind(t *testing.T) {
Kind: "another kind",
Key: "0x11111111111111",
}
clearReq := &OffchainLocalStorageClear{
Kind: "another kind",
Key: "0x11111111111111",
}
err := m.LocalStorageSet(nil, setReq, nil)
require.Error(t, err, "storage kind not found: another kind")

err = m.LocalStorageGet(nil, getReq, nil)
require.Error(t, err, "storage kind not found: another kind")

err = m.LocalStorageClear(nil, clearReq, nil)
require.Error(t, err, "storage kind not found: another kind")
}

func Test_OffchainModule_LocalStorageSet(t *testing.T) {
Expand Down
90 changes: 90 additions & 0 deletions dot/rpc/modules/offchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,96 @@ import (
"github.com/stretchr/testify/assert"
)

func TestOffchainModule_LocalStorageClear(t *testing.T) {
ctrl := gomock.NewController(t)

mockRuntimeStorageAPI := mocks.NewMockRuntimeStorageAPI(ctrl)
mockRuntimeStorageAPI.EXPECT().ClearPersistent(common.MustHexToBytes("0x11111111111111")).
Return(errors.New("ClearPersistent error"))
mockRuntimeStorageAPI.EXPECT().ClearLocal(common.MustHexToBytes("0x11111111111111")).Return(nil)
offChainModule := NewOffchainModule(mockRuntimeStorageAPI)

type fields struct {
nodeStorage RuntimeStorageAPI
}
type args struct {
in0 *http.Request
req *OffchainLocalStorageClear
}
tests := []struct {
name string
fields fields
args args
expErr error
}{
{
name: "ClearPersistent_error",
fields: fields{
offChainModule.nodeStorage,
},
args: args{
req: &OffchainLocalStorageClear{
Kind: offchainPersistent,
Key: "0x11111111111111",
},
},
expErr: errors.New("ClearPersistent error"),
},
{
name: "Invalid_Storage_Kind",
fields: fields{
offChainModule.nodeStorage,
},
args: args{
req: &OffchainLocalStorageClear{
Kind: "invalid kind",
Key: "0x11111111111111",
},
},
expErr: fmt.Errorf("storage kind not found: invalid kind"),
},
{
name: "ClearLocal_OK",
fields: fields{
offChainModule.nodeStorage,
},
args: args{
req: &OffchainLocalStorageClear{
Kind: offchainLocal,
Key: "0x11111111111111",
},
},
},
{
name: "Invalid_key",
fields: fields{
offChainModule.nodeStorage,
},
args: args{
req: &OffchainLocalStorageClear{
Kind: offchainLocal,
Key: "0x1",
},
},
expErr: errors.New("encoding/hex: odd length hex string: 0x1"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &OffchainModule{
nodeStorage: tt.fields.nodeStorage,
}
res := StringResponse("")
err := s.LocalStorageClear(tt.args.in0, tt.args.req, &res)
if tt.expErr != nil {
assert.EqualError(t, err, tt.expErr.Error())
} else {
assert.NoError(t, err)
}
})
}
}

func TestOffchainModule_LocalStorageGet(t *testing.T) {
ctrl := gomock.NewController(t)

Expand Down
10 changes: 10 additions & 0 deletions lib/runtime/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func (n *NodeStorage) GetLocal(k []byte) ([]byte, error) {
return n.LocalStorage.Get(k)
}

// ClearLocal removes a key and value from LOCAL node storage
func (n *NodeStorage) ClearLocal(k []byte) error {
return n.LocalStorage.Del(k)
}

// SetPersistent persists a key and value into PERSISTENT node storage
func (n *NodeStorage) SetPersistent(k, v []byte) error {
return n.PersistentStorage.Put(k, v)
Expand All @@ -47,6 +52,11 @@ func (n *NodeStorage) GetPersistent(k []byte) ([]byte, error) {
return n.PersistentStorage.Get(k)
}

// ClearPersistent removes a key and value from PERSISTENT node storage
func (n *NodeStorage) ClearPersistent(k []byte) error {
return n.PersistentStorage.Del(k)
}

type Allocator interface {
Allocate(mem Memory, size uint32) (uint32, error)
Deallocate(mem Memory, ptr uint32) error
Expand Down
35 changes: 9 additions & 26 deletions tests/rpc/rpc_04-offchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,25 @@ import (
)

func TestOffchainRPC(t *testing.T) { //nolint:tparallel
t.SkipNow() // TODO

genesisPath := libutils.GetWestendDevRawGenesisPath(t)
tomlConfig := config.Default()
tomlConfig.ChainSpec = genesisPath
node := node.New(t, tomlConfig)
ctx, cancel := context.WithCancel(context.Background())
node.InitAndStartTest(ctx, t, cancel)

t.Run("offchain_localStorageSet", func(t *testing.T) {
t.Parallel()

var response struct{} // TODO

fetchWithTimeout(ctx, t, "offchain_localStorageSet", "", &response)
// t.Run("offchain_localStorageSet", ...) // TODO
// t.Run("offchain_localStorageGet", ...) // TODO

// TODO assert response
})

t.Run("offchain_localStorageGet", func(t *testing.T) {
t.Run("offchain_localStorageClear", func(t *testing.T) {
t.Parallel()

var response struct{} // TODO

fetchWithTimeout(ctx, t, "offchain_localStorageGet", "", &response)

// TODO assert response
})

t.Run("offchain_localStorageGet", func(t *testing.T) {
t.Parallel()

var response struct{} // TODO

fetchWithTimeout(ctx, t, "offchain_localStorageGet", "", &response)
var setResponse any
fetchWithTimeout(ctx, t, "offchain_localStorageSet",
`["PERSISTENT", "0x11111111111111", "0x22222222222222"]`, &setResponse)

// TODO assert response
var clearResponse any
fetchWithTimeout(ctx, t, "offchain_localStorageClear",
`["PERSISTENT", "0x11111111111111"]`, &clearResponse)
})
}