Skip to content

Commit a91de8b

Browse files
feat(dot/rpc) implement offchain_localStorageSet and offchain_localStorageGet (ChainSafe#1774)
* feat: add offchain set and get RPC methods * chore: implement unit tests * chore: fix deepsource * chore: fix mocks deepsource warnings * chore: fix deepsource warns * chore: remove control param * chore: remove os.Exit() call outside main func * chore: increase the test coverage * chore: remove comment
1 parent 44b7216 commit a91de8b

File tree

13 files changed

+380
-40
lines changed

13 files changed

+380
-40
lines changed

cmd/gossamer/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ func setDotGlobalConfigFromFlags(ctx *cli.Context, cfg *dot.GlobalConfig) {
493493

494494
func setDotGlobalConfigName(ctx *cli.Context, tomlCfg *ctoml.Config, cfg *dot.GlobalConfig) error {
495495
globalBasePath := utils.ExpandDir(cfg.BasePath)
496-
initialised := dot.NodeInitialized(globalBasePath, false)
496+
initialised := dot.NodeInitialized(globalBasePath)
497497

498498
// consider the --name flag as higher priority
499499
if ctx.GlobalString(NameFlag.Name) != "" {

cmd/gossamer/main.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,7 @@ func gossamerAction(ctx *cli.Context) error {
240240
// from createDotConfig because dot config should not include expanded path)
241241
cfg.Global.BasePath = utils.ExpandDir(cfg.Global.BasePath)
242242

243-
// check if node has not been initialised (expected true - add warning log)
244-
if !dot.NodeInitialized(cfg.Global.BasePath, true) {
245-
243+
if !dot.NodeInitialized(cfg.Global.BasePath) {
246244
// initialise node (initialise state database and load genesis data)
247245
err = dot.InitNode(cfg)
248246
if err != nil {
@@ -334,7 +332,7 @@ func initAction(ctx *cli.Context) error {
334332
// from createDotConfig because dot config should not include expanded path)
335333
cfg.Global.BasePath = utils.ExpandDir(cfg.Global.BasePath)
336334
// check if node has been initialised (expected false - no warning log)
337-
if dot.NodeInitialized(cfg.Global.BasePath, false) {
335+
if dot.NodeInitialized(cfg.Global.BasePath) {
338336

339337
// use --force value to force initialise the node
340338
force := ctx.Bool(ForceFlag.Name)

dot/node.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,18 @@ func InitNode(cfg *Config) error {
130130

131131
// NodeInitialized returns true if, within the configured data directory for the
132132
// node, the state database has been created and the genesis data has been loaded
133-
func NodeInitialized(basepath string, expected bool) bool {
133+
func NodeInitialized(basepath string) bool {
134134
// check if key registry exists
135135
registry := path.Join(basepath, utils.DefaultDatabaseDir, "KEYREGISTRY")
136136

137137
_, err := os.Stat(registry)
138138
if os.IsNotExist(err) {
139-
if expected {
140-
logger.Debug(
141-
"node has not been initialised",
142-
"basepath", basepath,
143-
"error", "failed to locate KEYREGISTRY file in data directory",
144-
)
145-
}
139+
logger.Debug(
140+
"node has not been initialised",
141+
"basepath", basepath,
142+
"error", "failed to locate KEYREGISTRY file in data directory",
143+
)
144+
146145
return false
147146
}
148147

@@ -256,7 +255,12 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
256255
}
257256

258257
// create runtime
259-
err = loadRuntime(cfg, stateSrvc, ks, networkSrvc)
258+
ns, err := createRuntimeStorage(stateSrvc)
259+
if err != nil {
260+
return nil, err
261+
}
262+
263+
err = loadRuntime(cfg, ns, stateSrvc, ks, networkSrvc)
260264
if err != nil {
261265
return nil, err
262266
}
@@ -308,7 +312,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,
308312

309313
// check if rpc service is enabled
310314
if enabled := cfg.RPC.isRPCEnabled() || cfg.RPC.isWSEnabled(); enabled {
311-
rpcSrvc := createRPCService(cfg, stateSrvc, coreSrvc, networkSrvc, bp, sysSrvc, fg)
315+
rpcSrvc := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, bp, sysSrvc, fg)
312316
nodeSrvcs = append(nodeSrvcs, rpcSrvc)
313317
} else {
314318
logger.Debug("rpc service disabled by default", "rpc", enabled)
@@ -401,17 +405,16 @@ func (n *Node) Start() error {
401405
// start all dot node services
402406
n.Services.StartAll()
403407

408+
n.wg.Add(1)
404409
go func() {
405410
sigc := make(chan os.Signal, 1)
406411
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
407412
defer signal.Stop(sigc)
408413
<-sigc
409414
logger.Info("signal interrupt, shutting down...")
410415
n.Stop()
411-
os.Exit(130)
412416
}()
413417

414-
n.wg.Add(1)
415418
close(n.started)
416419
n.wg.Wait()
417420
return nil
@@ -428,7 +431,7 @@ func (n *Node) Stop() {
428431
n.wg.Done()
429432
}
430433

431-
func loadRuntime(cfg *Config, stateSrvc *state.Service, ks *keystore.GlobalKeystore, net *network.Service) error {
434+
func loadRuntime(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Service, ks *keystore.GlobalKeystore, net *network.Service) error {
432435
blocks := stateSrvc.Block.GetNonFinalisedBlocks()
433436
runtimeCode := make(map[string]runtime.Instance)
434437
for i := range blocks {
@@ -448,7 +451,7 @@ func loadRuntime(cfg *Config, stateSrvc *state.Service, ks *keystore.GlobalKeyst
448451
continue
449452
}
450453

451-
rt, err := createRuntime(cfg, stateSrvc, ks, net, code)
454+
rt, err := createRuntime(cfg, *ns, stateSrvc, ks, net, code)
452455
if err != nil {
453456
return err
454457
}

dot/node_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,13 @@ func TestNodeInitialized(t *testing.T) {
8282

8383
cfg.Init.Genesis = genFile.Name()
8484

85-
expected := NodeInitialized(cfg.Global.BasePath, false)
85+
expected := NodeInitialized(cfg.Global.BasePath)
8686
require.Equal(t, expected, false)
8787

8888
err := InitNode(cfg)
8989
require.NoError(t, err)
9090

91-
expected = NodeInitialized(cfg.Global.BasePath, true)
91+
expected = NodeInitialized(cfg.Global.BasePath)
9292
require.Equal(t, expected, true)
9393
}
9494

dot/rpc/http.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/ChainSafe/gossamer/dot/rpc/modules"
2727
"github.com/ChainSafe/gossamer/dot/rpc/subscription"
2828
"github.com/ChainSafe/gossamer/lib/common"
29+
"github.com/ChainSafe/gossamer/lib/runtime"
2930
log "github.com/ChainSafe/log15"
3031
"github.com/go-playground/validator/v10"
3132
"github.com/gorilla/mux"
@@ -53,6 +54,7 @@ type HTTPServerConfig struct {
5354
TransactionQueueAPI modules.TransactionStateAPI
5455
RPCAPI modules.RPCAPI
5556
SystemAPI modules.SystemAPI
57+
NodeStorage *runtime.NodeStorage
5658
RPC bool
5759
RPCExternal bool
5860
RPCUnsafe bool
@@ -104,7 +106,6 @@ func NewHTTPServer(cfg *HTTPServerConfig) *HTTPServer {
104106

105107
// RegisterModules registers the RPC services associated with the given API modules
106108
func (h *HTTPServer) RegisterModules(mods []string) {
107-
108109
for _, mod := range mods {
109110
h.logger.Debug("Enabling rpc module", "module", mod)
110111
var srvc interface{}
@@ -124,6 +125,8 @@ func (h *HTTPServer) RegisterModules(mods []string) {
124125
srvc = modules.NewRPCModule(h.serverConfig.RPCAPI)
125126
case "dev":
126127
srvc = modules.NewDevModule(h.serverConfig.BlockProducerAPI, h.serverConfig.NetworkAPI)
128+
case "offchain":
129+
srvc = modules.NewOffchainModule(h.serverConfig.NodeStorage)
127130
default:
128131
h.logger.Warn("Unrecognised module", "module", mod)
129132
continue

dot/rpc/modules/api.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,11 @@ type BlockFinalityAPI interface {
109109
PreVotes() []ed25519.PublicKeyBytes
110110
PreCommits() []ed25519.PublicKeyBytes
111111
}
112+
113+
// RuntimeStorageAPI is the interface to interacts with the node storage
114+
type RuntimeStorageAPI interface {
115+
SetLocal(k, v []byte) error
116+
SetPersistent(k, v []byte) error
117+
GetLocal(k []byte) ([]byte, error)
118+
GetPersistent(k []byte) ([]byte, error)
119+
}

dot/rpc/modules/mocks/runtime_storage_api.go

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dot/rpc/modules/offchain.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package modules
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/ChainSafe/gossamer/lib/common"
8+
)
9+
10+
const (
11+
offchainPersistent = "PERSISTENT"
12+
offchainLocal = "LOCAL"
13+
)
14+
15+
// OffchainLocalStorageGet represents the request format to retrieve data from offchain storage
16+
type OffchainLocalStorageGet struct {
17+
Kind string
18+
Key string
19+
}
20+
21+
// OffchainLocalStorageSet represents the request format to store data into offchain storage
22+
type OffchainLocalStorageSet struct {
23+
Kind string
24+
Key string
25+
Value string
26+
}
27+
28+
// OffchainModule defines the RPC module to Offchain methods
29+
type OffchainModule struct {
30+
nodeStorage RuntimeStorageAPI
31+
}
32+
33+
// NewOffchainModule creates a RPC module to Offchain methods
34+
func NewOffchainModule(ns RuntimeStorageAPI) *OffchainModule {
35+
return &OffchainModule{
36+
nodeStorage: ns,
37+
}
38+
}
39+
40+
// LocalStorageGet get offchain local storage under given key and prefix
41+
func (s *OffchainModule) LocalStorageGet(_ *http.Request, req *OffchainLocalStorageGet, res *StringResponse) error {
42+
var (
43+
v []byte
44+
key []byte
45+
err error
46+
)
47+
48+
if key, err = common.HexToBytes(req.Key); err != nil {
49+
return err
50+
}
51+
52+
switch req.Kind {
53+
case offchainPersistent:
54+
v, err = s.nodeStorage.GetPersistent(key)
55+
case offchainLocal:
56+
v, err = s.nodeStorage.GetLocal(key)
57+
default:
58+
return fmt.Errorf("storage kind not found: %s", req.Kind)
59+
}
60+
61+
if err != nil {
62+
return err
63+
}
64+
65+
*res = StringResponse(common.BytesToHex(v))
66+
return nil
67+
}
68+
69+
// LocalStorageSet set offchain local storage under given key and prefix
70+
func (s *OffchainModule) LocalStorageSet(_ *http.Request, req *OffchainLocalStorageSet, _ *StringResponse) error {
71+
var (
72+
val []byte
73+
key []byte
74+
err error
75+
)
76+
77+
if key, err = common.HexToBytes(req.Key); err != nil {
78+
return err
79+
}
80+
81+
if val, err = common.HexToBytes(req.Value); err != nil {
82+
return err
83+
}
84+
85+
switch req.Kind {
86+
case offchainPersistent:
87+
err = s.nodeStorage.SetPersistent(key, val)
88+
case offchainLocal:
89+
err = s.nodeStorage.SetLocal(key, val)
90+
default:
91+
return fmt.Errorf("storage kind not found: %s", req.Kind)
92+
}
93+
94+
if err != nil {
95+
return err
96+
}
97+
98+
return nil
99+
}

0 commit comments

Comments
 (0)