Skip to content

Commit

Permalink
feat(dot/rpc) implement offchain_localStorageSet and `offchain_loca…
Browse files Browse the repository at this point in the history
…lStorageGet` (#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
  • Loading branch information
EclesioMeloJunior authored Sep 9, 2021
1 parent 44b7216 commit a91de8b
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 40 deletions.
2 changes: 1 addition & 1 deletion cmd/gossamer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ func setDotGlobalConfigFromFlags(ctx *cli.Context, cfg *dot.GlobalConfig) {

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

// consider the --name flag as higher priority
if ctx.GlobalString(NameFlag.Name) != "" {
Expand Down
6 changes: 2 additions & 4 deletions cmd/gossamer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,7 @@ func gossamerAction(ctx *cli.Context) error {
// from createDotConfig because dot config should not include expanded path)
cfg.Global.BasePath = utils.ExpandDir(cfg.Global.BasePath)

// check if node has not been initialised (expected true - add warning log)
if !dot.NodeInitialized(cfg.Global.BasePath, true) {

if !dot.NodeInitialized(cfg.Global.BasePath) {
// initialise node (initialise state database and load genesis data)
err = dot.InitNode(cfg)
if err != nil {
Expand Down Expand Up @@ -334,7 +332,7 @@ func initAction(ctx *cli.Context) error {
// from createDotConfig because dot config should not include expanded path)
cfg.Global.BasePath = utils.ExpandDir(cfg.Global.BasePath)
// check if node has been initialised (expected false - no warning log)
if dot.NodeInitialized(cfg.Global.BasePath, false) {
if dot.NodeInitialized(cfg.Global.BasePath) {

// use --force value to force initialise the node
force := ctx.Bool(ForceFlag.Name)
Expand Down
31 changes: 17 additions & 14 deletions dot/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,18 @@ func InitNode(cfg *Config) error {

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

_, err := os.Stat(registry)
if os.IsNotExist(err) {
if expected {
logger.Debug(
"node has not been initialised",
"basepath", basepath,
"error", "failed to locate KEYREGISTRY file in data directory",
)
}
logger.Debug(
"node has not been initialised",
"basepath", basepath,
"error", "failed to locate KEYREGISTRY file in data directory",
)

return false
}

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

// create runtime
err = loadRuntime(cfg, stateSrvc, ks, networkSrvc)
ns, err := createRuntimeStorage(stateSrvc)
if err != nil {
return nil, err
}

err = loadRuntime(cfg, ns, stateSrvc, ks, networkSrvc)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -308,7 +312,7 @@ func NewNode(cfg *Config, ks *keystore.GlobalKeystore, stopFunc func()) (*Node,

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

n.wg.Add(1)
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigc)
<-sigc
logger.Info("signal interrupt, shutting down...")
n.Stop()
os.Exit(130)
}()

n.wg.Add(1)
close(n.started)
n.wg.Wait()
return nil
Expand All @@ -428,7 +431,7 @@ func (n *Node) Stop() {
n.wg.Done()
}

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

rt, err := createRuntime(cfg, stateSrvc, ks, net, code)
rt, err := createRuntime(cfg, *ns, stateSrvc, ks, net, code)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions dot/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ func TestNodeInitialized(t *testing.T) {

cfg.Init.Genesis = genFile.Name()

expected := NodeInitialized(cfg.Global.BasePath, false)
expected := NodeInitialized(cfg.Global.BasePath)
require.Equal(t, expected, false)

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

expected = NodeInitialized(cfg.Global.BasePath, true)
expected = NodeInitialized(cfg.Global.BasePath)
require.Equal(t, expected, true)
}

Expand Down
5 changes: 4 additions & 1 deletion dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/ChainSafe/gossamer/dot/rpc/modules"
"github.com/ChainSafe/gossamer/dot/rpc/subscription"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/runtime"
log "github.com/ChainSafe/log15"
"github.com/go-playground/validator/v10"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -53,6 +54,7 @@ type HTTPServerConfig struct {
TransactionQueueAPI modules.TransactionStateAPI
RPCAPI modules.RPCAPI
SystemAPI modules.SystemAPI
NodeStorage *runtime.NodeStorage
RPC bool
RPCExternal bool
RPCUnsafe bool
Expand Down Expand Up @@ -104,7 +106,6 @@ func NewHTTPServer(cfg *HTTPServerConfig) *HTTPServer {

// RegisterModules registers the RPC services associated with the given API modules
func (h *HTTPServer) RegisterModules(mods []string) {

for _, mod := range mods {
h.logger.Debug("Enabling rpc module", "module", mod)
var srvc interface{}
Expand All @@ -124,6 +125,8 @@ func (h *HTTPServer) RegisterModules(mods []string) {
srvc = modules.NewRPCModule(h.serverConfig.RPCAPI)
case "dev":
srvc = modules.NewDevModule(h.serverConfig.BlockProducerAPI, h.serverConfig.NetworkAPI)
case "offchain":
srvc = modules.NewOffchainModule(h.serverConfig.NodeStorage)
default:
h.logger.Warn("Unrecognised module", "module", mod)
continue
Expand Down
8 changes: 8 additions & 0 deletions dot/rpc/modules/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,11 @@ type BlockFinalityAPI interface {
PreVotes() []ed25519.PublicKeyBytes
PreCommits() []ed25519.PublicKeyBytes
}

// RuntimeStorageAPI is the interface to interacts with the node storage
type RuntimeStorageAPI interface {
SetLocal(k, v []byte) error
SetPersistent(k, v []byte) error
GetLocal(k []byte) ([]byte, error)
GetPersistent(k []byte) ([]byte, error)
}
80 changes: 80 additions & 0 deletions dot/rpc/modules/mocks/runtime_storage_api.go

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

99 changes: 99 additions & 0 deletions dot/rpc/modules/offchain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package modules

import (
"fmt"
"net/http"

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

const (
offchainPersistent = "PERSISTENT"
offchainLocal = "LOCAL"
)

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

// OffchainLocalStorageSet represents the request format to store data into offchain storage
type OffchainLocalStorageSet struct {
Kind string
Key string
Value string
}

// OffchainModule defines the RPC module to Offchain methods
type OffchainModule struct {
nodeStorage RuntimeStorageAPI
}

// NewOffchainModule creates a RPC module to Offchain methods
func NewOffchainModule(ns RuntimeStorageAPI) *OffchainModule {
return &OffchainModule{
nodeStorage: ns,
}
}

// LocalStorageGet get offchain local storage under given key and prefix
func (s *OffchainModule) LocalStorageGet(_ *http.Request, req *OffchainLocalStorageGet, res *StringResponse) error {
var (
v []byte
key []byte
err error
)

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

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

if err != nil {
return err
}

*res = StringResponse(common.BytesToHex(v))
return nil
}

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

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

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

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

if err != nil {
return err
}

return nil
}
Loading

0 comments on commit a91de8b

Please sign in to comment.