From 2186caf2370efe64afc5211cafc87e35f01d22ca Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Fri, 22 Oct 2021 15:06:50 +0530 Subject: [PATCH] feat(dot/rpc): implement sync_state_genSyncSpec RPC call (#1827) --- chain/dev/config.toml | 2 +- chain/dev/defaults.go | 2 +- chain/gssmr/config.toml | 2 +- chain/gssmr/defaults.go | 2 +- chain/kusama/config.toml | 2 +- chain/kusama/defaults.go | 2 +- chain/polkadot/config.toml | 2 +- chain/polkadot/defaults.go | 2 +- dot/build_spec.go | 6 +- dot/node.go | 7 +- dot/rpc/http.go | 4 +- dot/rpc/http_test.go | 4 +- dot/rpc/modules/api.go | 6 ++ dot/rpc/modules/sync_state.go | 102 +++++++++++++++++++++++++++++ dot/rpc/modules/sync_state_test.go | 35 ++++++++++ dot/rpc/service.go | 2 +- dot/services.go | 15 ++++- dot/services_test.go | 6 +- tests/data/db/config.toml | 2 +- 19 files changed, 182 insertions(+), 23 deletions(-) create mode 100644 dot/rpc/modules/sync_state.go create mode 100644 dot/rpc/modules/sync_state_test.go diff --git a/chain/dev/config.toml b/chain/dev/config.toml index dc2abc035f..d6023875d8 100644 --- a/chain/dev/config.toml +++ b/chain/dev/config.toml @@ -36,5 +36,5 @@ enabled = true ws = true port = 8545 host = "localhost" -modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"] +modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"] ws-port = 8546 diff --git a/chain/dev/defaults.go b/chain/dev/defaults.go index 40134204d0..c439084f41 100644 --- a/chain/dev/defaults.go +++ b/chain/dev/defaults.go @@ -87,7 +87,7 @@ var ( // DefaultRPCHTTPPort rpc port DefaultRPCHTTPPort = uint32(8545) // DefaultRPCModules rpc modules - DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"} + DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"} // DefaultRPCWSPort rpc websocket port DefaultRPCWSPort = uint32(8546) // DefaultRPCEnabled enables the RPC server diff --git a/chain/gssmr/config.toml b/chain/gssmr/config.toml index 3f12e89ac5..8f15cf520a 100644 --- a/chain/gssmr/config.toml +++ b/chain/gssmr/config.toml @@ -36,5 +36,5 @@ min-peers = 1 enabled = false port = 8545 host = "localhost" -modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"] +modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"] ws-port = 8546 diff --git a/chain/gssmr/defaults.go b/chain/gssmr/defaults.go index 910891c4bb..446bcc0f0e 100644 --- a/chain/gssmr/defaults.go +++ b/chain/gssmr/defaults.go @@ -94,7 +94,7 @@ var ( // DefaultRPCHTTPPort rpc port DefaultRPCHTTPPort = uint32(8545) // DefaultRPCModules rpc modules - DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"} + DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"} // DefaultRPCWSPort rpc websocket port DefaultRPCWSPort = uint32(8546) ) diff --git a/chain/kusama/config.toml b/chain/kusama/config.toml index 035cad0c37..9a1a17e76c 100644 --- a/chain/kusama/config.toml +++ b/chain/kusama/config.toml @@ -35,7 +35,7 @@ enabled = false external = false port = 8545 host = "localhost" -modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"] +modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"] ws-port = 8546 ws = false ws-external = false diff --git a/chain/kusama/defaults.go b/chain/kusama/defaults.go index ed8f3365fd..64abb6129e 100644 --- a/chain/kusama/defaults.go +++ b/chain/kusama/defaults.go @@ -83,7 +83,7 @@ var ( // DefaultRPCHTTPPort rpc port DefaultRPCHTTPPort = uint32(8545) // DefaultRPCModules rpc modules - DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"} + DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"} // DefaultRPCWSPort rpc websocket port DefaultRPCWSPort = uint32(8546) ) diff --git a/chain/polkadot/config.toml b/chain/polkadot/config.toml index 440f0b12bd..b44cf55a94 100644 --- a/chain/polkadot/config.toml +++ b/chain/polkadot/config.toml @@ -34,5 +34,5 @@ nomdns = false enabled = false port = 8545 host = "localhost" -modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"] +modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"] ws-port = 8546 \ No newline at end of file diff --git a/chain/polkadot/defaults.go b/chain/polkadot/defaults.go index 31853bb83a..0eddbfee0a 100644 --- a/chain/polkadot/defaults.go +++ b/chain/polkadot/defaults.go @@ -84,7 +84,7 @@ var ( // DefaultRPCHTTPPort rpc port DefaultRPCHTTPPort = uint32(8545) // DefaultRPCModules rpc modules - DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "payment"} + DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate", "payment"} // DefaultRPCWSPort rpc websocket port DefaultRPCWSPort = uint32(8546) ) diff --git a/dot/build_spec.go b/dot/build_spec.go index f29fcce8f4..1b0e26e5d3 100644 --- a/dot/build_spec.go +++ b/dot/build_spec.go @@ -142,13 +142,9 @@ func BuildFromDB(path string) (*BuildSpec, error) { } tmpGen.Name = gData.Name tmpGen.ID = gData.ID - tmpGen.Bootnodes = make([]string, len(gData.Bootnodes)) + tmpGen.Bootnodes = common.BytesToStringArray(gData.Bootnodes) tmpGen.ProtocolID = gData.ProtocolID - for i, bn := range gData.Bootnodes { - tmpGen.Bootnodes[i] = string(bn) - } - bs := &BuildSpec{ genesis: tmpGen, } diff --git a/dot/node.go b/dot/node.go index 6b30f4784c..df0c665a9d 100644 --- a/dot/node.go +++ b/dot/node.go @@ -30,6 +30,7 @@ import ( "github.com/ChainSafe/gossamer/dot/metrics" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/rpc" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/state/pruner" "github.com/ChainSafe/gossamer/dot/telemetry" @@ -313,7 +314,11 @@ 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, ns, stateSrvc, coreSrvc, networkSrvc, bp, sysSrvc, fg) + var rpcSrvc *rpc.HTTPServer + rpcSrvc, err = createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, bp, sysSrvc, fg) + if err != nil { + return nil, fmt.Errorf("failed to create rpc service: %s", err) + } nodeSrvcs = append(nodeSrvcs, rpcSrvc) } else { logger.Debug("rpc service disabled by default", "rpc", enabled) diff --git a/dot/rpc/http.go b/dot/rpc/http.go index 57388903c2..21b6838cdd 100644 --- a/dot/rpc/http.go +++ b/dot/rpc/http.go @@ -54,6 +54,7 @@ type HTTPServerConfig struct { TransactionQueueAPI modules.TransactionStateAPI RPCAPI modules.RPCAPI SystemAPI modules.SystemAPI + SyncStateAPI modules.SyncStateAPI NodeStorage *runtime.NodeStorage RPC bool RPCExternal bool @@ -129,6 +130,8 @@ func (h *HTTPServer) RegisterModules(mods []string) { srvc = modules.NewOffchainModule(h.serverConfig.NodeStorage) case "childstate": srvc = modules.NewChildStateModule(h.serverConfig.StorageAPI, h.serverConfig.BlockAPI) + case "syncstate": + srvc = modules.NewSyncStateModule(h.serverConfig.SyncStateAPI) case "payment": srvc = modules.NewPaymentModule(h.serverConfig.BlockAPI) default: @@ -137,7 +140,6 @@ func (h *HTTPServer) RegisterModules(mods []string) { } err := h.rpcServer.RegisterService(srvc, mod) - if err != nil { h.logger.Warn("Failed to register module", "mod", mod, "err", err) } diff --git a/dot/rpc/http_test.go b/dot/rpc/http_test.go index b2dfb50b01..2fc7f0d11d 100644 --- a/dot/rpc/http_test.go +++ b/dot/rpc/http_test.go @@ -41,7 +41,7 @@ import ( func TestRegisterModules(t *testing.T) { rpcapiMocks := new(mocks.MockRPCAPI) - mods := []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"} + mods := []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate", "syncstate"} for _, modName := range mods { rpcapiMocks.On("BuildMethodNames", mock.Anything, modName).Once() @@ -125,7 +125,7 @@ func TestNewHTTPServer(t *testing.T) { func TestUnsafeRPCProtection(t *testing.T) { cfg := &HTTPServerConfig{ - Modules: []string{"system", "author", "chain", "state", "rpc", "grandpa", "dev"}, + Modules: []string{"system", "author", "chain", "state", "rpc", "grandpa", "dev", "syncstate"}, RPCPort: 7878, RPCAPI: NewService(), RPCUnsafe: false, diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 6ad000cb75..ed7000bf71 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -9,6 +9,7 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" + "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/transaction" @@ -122,3 +123,8 @@ type RuntimeStorageAPI interface { GetLocal(k []byte) ([]byte, error) GetPersistent(k []byte) ([]byte, error) } + +// SyncStateAPI is the interface to interact with sync state. +type SyncStateAPI interface { + GenSyncSpec(raw bool) (*genesis.Genesis, error) +} diff --git a/dot/rpc/modules/sync_state.go b/dot/rpc/modules/sync_state.go new file mode 100644 index 0000000000..90d2463056 --- /dev/null +++ b/dot/rpc/modules/sync_state.go @@ -0,0 +1,102 @@ +// Copyright 2019 ChainSafe Systems (ON) Corp. +// This file is part of gossamer. +// +// The gossamer library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The gossamer library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the gossamer library. If not, see . + +package modules + +import ( + "net/http" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/genesis" +) + +// GenSyncSpecRequest represents request to get chain specification. +type GenSyncSpecRequest struct { + Raw bool +} + +// SyncStateModule is an RPC module to interact with sync state methods. +type SyncStateModule struct { + syncStateAPI SyncStateAPI +} + +// NewSyncStateModule creates an instance of SyncStateModule given SyncStateAPI. +func NewSyncStateModule(syncStateAPI SyncStateAPI) *SyncStateModule { + return &SyncStateModule{syncStateAPI: syncStateAPI} +} + +// GenSyncSpec returns the JSON serialised chain specification running the node +// (i.e. the current state state), with a sync state. +func (ss *SyncStateModule) GenSyncSpec(_ *http.Request, req *GenSyncSpecRequest, res *genesis.Genesis) error { + g, err := ss.syncStateAPI.GenSyncSpec(req.Raw) + if err != nil { + return err + } + + *res = *g + return nil +} + +// syncState implements SyncStateAPI. +type syncState struct { + chainSpecification *genesis.Genesis +} + +// NewStateSync creates an instance of SyncStateAPI given a chain specification. +func NewStateSync(gData *genesis.Data, storageAPI StorageAPI) (SyncStateAPI, error) { + tmpGen := &genesis.Genesis{ + Name: "", + ID: "", + Bootnodes: nil, + ProtocolID: "", + Genesis: genesis.Fields{ + Runtime: nil, + }, + } + tmpGen.Genesis.Raw = make(map[string]map[string]string) + tmpGen.Genesis.Runtime = make(map[string]map[string]interface{}) + + // set genesis fields data + ent, err := storageAPI.Entries(nil) + if err != nil { + return nil, err + } + + err = genesis.BuildFromMap(ent, tmpGen) + if err != nil { + return nil, err + } + + tmpGen.Name = gData.Name + tmpGen.ID = gData.ID + tmpGen.Bootnodes = common.BytesToStringArray(gData.Bootnodes) + tmpGen.ProtocolID = gData.ProtocolID + + return syncState{chainSpecification: tmpGen}, nil +} + +// GenSyncSpec returns the JSON serialised chain specification running the node +// (i.e. the current state), with a sync state. +func (s syncState) GenSyncSpec(raw bool) (*genesis.Genesis, error) { + if raw { + err := s.chainSpecification.ToRaw() + if err != nil { + return nil, err + } + } + + return s.chainSpecification, nil +} diff --git a/dot/rpc/modules/sync_state_test.go b/dot/rpc/modules/sync_state_test.go new file mode 100644 index 0000000000..2b6f800887 --- /dev/null +++ b/dot/rpc/modules/sync_state_test.go @@ -0,0 +1,35 @@ +package modules + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/stretchr/testify/require" +) + +const GssmrGenesisPath = "../../../chain/gssmr/genesis.json" + +func TestSyncStateModule(t *testing.T) { + fp, err := filepath.Abs(GssmrGenesisPath) + require.NoError(t, err) + + data, err := ioutil.ReadFile(filepath.Clean(fp)) + require.NoError(t, err) + + g := new(genesis.Genesis) + err = json.Unmarshal(data, g) + require.NoError(t, err) + + module := NewSyncStateModule(syncState{chainSpecification: g}) + + req := GenSyncSpecRequest{ + Raw: true, + } + var res genesis.Genesis + + err = module.GenSyncSpec(nil, &req, &res) + require.NoError(t, err) +} diff --git a/dot/rpc/service.go b/dot/rpc/service.go index 2ea764a7b5..01773a305d 100644 --- a/dot/rpc/service.go +++ b/dot/rpc/service.go @@ -49,7 +49,7 @@ var ( ) // BuildMethodNames takes receiver interface and populates rpcMethods array with available -// method names +// method names func (s *Service) BuildMethodNames(rcvr interface{}, name string) { rcvrType := reflect.TypeOf(rcvr) for i := 0; i < rcvrType.NumMethod(); i++ { diff --git a/dot/services.go b/dot/services.go index 4e124e5822..073afd7b43 100644 --- a/dot/services.go +++ b/dot/services.go @@ -291,7 +291,7 @@ func createNetworkService(cfg *Config, stateSrvc *state.Service) (*network.Servi // RPC Service // createRPCService creates the RPC service from the provided core configuration -func createRPCService(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, bp modules.BlockProducerAPI, sysSrvc *system.Service, finSrvc *grandpa.Service) *rpc.HTTPServer { +func createRPCService(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Service, coreSrvc *core.Service, networkSrvc *network.Service, bp modules.BlockProducerAPI, sysSrvc *system.Service, finSrvc *grandpa.Service) (*rpc.HTTPServer, error) { logger.Info( "creating rpc service...", "host", cfg.RPC.Host, @@ -304,6 +304,16 @@ func createRPCService(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Ser ) rpcService := rpc.NewService() + genesisData, err := stateSrvc.Base.LoadGenesisData() + if err != nil { + return nil, fmt.Errorf("failed to load genesis data: %s", err) + } + + syncStateSrvc, err := modules.NewStateSync(genesisData, stateSrvc.Storage) + if err != nil { + return nil, fmt.Errorf("failed to create sync state service: %s", err) + } + rpcConfig := &rpc.HTTPServerConfig{ LogLvl: cfg.Log.RPCLvl, BlockAPI: stateSrvc.Block, @@ -315,6 +325,7 @@ func createRPCService(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Ser BlockFinalityAPI: finSrvc, TransactionQueueAPI: stateSrvc.Transaction, RPCAPI: rpcService, + SyncStateAPI: syncStateSrvc, SystemAPI: sysSrvc, RPC: cfg.RPC.Enabled, RPCExternal: cfg.RPC.External, @@ -330,7 +341,7 @@ func createRPCService(cfg *Config, ns *runtime.NodeStorage, stateSrvc *state.Ser Modules: cfg.RPC.Modules, } - return rpc.NewHTTPServer(rpcConfig) + return rpc.NewHTTPServer(rpcConfig), nil } // createSystemService creates a systemService for providing system related information diff --git a/dot/services_test.go b/dot/services_test.go index 624cb8f2c3..7bd1071114 100644 --- a/dot/services_test.go +++ b/dot/services_test.go @@ -206,7 +206,8 @@ func TestCreateRPCService(t *testing.T) { sysSrvc, err := createSystemService(&cfg.System, stateSrvc) require.NoError(t, err) - rpcSrvc := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, nil, sysSrvc, nil) + rpcSrvc, err := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, nil, sysSrvc, nil) + require.NoError(t, err) require.NotNil(t, rpcSrvc) } @@ -342,7 +343,8 @@ func TestNewWebSocketServer(t *testing.T) { sysSrvc, err := createSystemService(&cfg.System, stateSrvc) require.NoError(t, err) - rpcSrvc := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, nil, sysSrvc, nil) + rpcSrvc, err := createRPCService(cfg, ns, stateSrvc, coreSrvc, networkSrvc, nil, sysSrvc, nil) + require.NoError(t, err) err = rpcSrvc.Start() require.Nil(t, err) diff --git a/tests/data/db/config.toml b/tests/data/db/config.toml index 7592d17e54..841fa35b2b 100644 --- a/tests/data/db/config.toml +++ b/tests/data/db/config.toml @@ -34,5 +34,5 @@ nomdns = false enabled = false port = 8545 host = "localhost" -modules = ["system", "author", "chain", "state", "rpc", "grandpa"] +modules = ["system", "author", "chain", "state", "rpc", "grandpa", "syncstate"] ws-port = 8546