Skip to content

Commit

Permalink
feat(dot/rpc): Implement childstate_getKeys rpc call (ChainSafe#1800)
Browse files Browse the repository at this point in the history
* feat: implement childstate_getKeys

* chore: finish unit tests

* chore: add childstate to http.go module init

* chore: address lint warns

* chore: addressing test issues

* chore: address deepsource complaints

* chore: apply changes and add copyright

* chore: use reflect.Pointer as Ptr was deprected

* chore: increase dot/rpc/http unit test coverage

* chore: revert Pointer to Ptr due to go version
  • Loading branch information
EclesioMeloJunior authored and timwu20 committed Dec 6, 2021
1 parent 619306d commit c5181ae
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 9 deletions.
2 changes: 1 addition & 1 deletion chain/dev/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ enabled = true
ws = true
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
ws-port = 8546
2 changes: 1 addition & 1 deletion chain/dev/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
// DefaultRPCEnabled enables the RPC server
Expand Down
2 changes: 1 addition & 1 deletion chain/gssmr/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ discovery-interval = 10
enabled = false
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
ws-port = 8546
2 changes: 1 addition & 1 deletion chain/gssmr/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
)
2 changes: 1 addition & 1 deletion chain/kusama/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ enabled = false
external = false
port = 8545
host = "localhost"
modules = ["system", "author", "chain", "state", "rpc", "grandpa"]
modules = ["system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"]
ws-port = 8546
ws = false
ws-external = false
2 changes: 1 addition & 1 deletion chain/kusama/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
)
2 changes: 1 addition & 1 deletion chain/polkadot/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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", "offchain", "childstate"]
ws-port = 8546
2 changes: 1 addition & 1 deletion chain/polkadot/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ var (
// DefaultRPCHTTPPort rpc port
DefaultRPCHTTPPort = uint32(8545)
// DefaultRPCModules rpc modules
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa"}
DefaultRPCModules = []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}
// DefaultRPCWSPort rpc websocket port
DefaultRPCWSPort = uint32(8546)
)
2 changes: 2 additions & 0 deletions dot/rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ func (h *HTTPServer) RegisterModules(mods []string) {
srvc = modules.NewDevModule(h.serverConfig.BlockProducerAPI, h.serverConfig.NetworkAPI)
case "offchain":
srvc = modules.NewOffchainModule(h.serverConfig.NodeStorage)
case "childstate":
srvc = modules.NewChildStateModule(h.serverConfig.StorageAPI, h.serverConfig.BlockAPI)
default:
h.logger.Warn("Unrecognised module", "module", mod)
continue
Expand Down
21 changes: 21 additions & 0 deletions dot/rpc/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ import (
"github.com/stretchr/testify/require"
)

func TestRegisterModules(t *testing.T) {
rpcapiMocks := new(mocks.MockRPCAPI)

mods := []string{"system", "author", "chain", "state", "rpc", "grandpa", "offchain", "childstate"}

for _, modName := range mods {
rpcapiMocks.On("BuildMethodNames", mock.Anything, modName).Once()
}

cfg := &HTTPServerConfig{
Modules: mods,
RPCAPI: rpcapiMocks,
}

NewHTTPServer(cfg)

for _, modName := range mods {
rpcapiMocks.AssertCalled(t, "BuildMethodNames", mock.Anything, modName)
}
}

func TestNewHTTPServer(t *testing.T) {
coreAPI := core.NewTestService(t, nil)
si := &types.SystemInfo{
Expand Down
2 changes: 2 additions & 0 deletions dot/rpc/modules/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"github.com/ChainSafe/gossamer/lib/grandpa"
"github.com/ChainSafe/gossamer/lib/runtime"
"github.com/ChainSafe/gossamer/lib/transaction"
"github.com/ChainSafe/gossamer/lib/trie"
)

// StorageAPI is the interface for the storage state
type StorageAPI interface {
GetStorage(root *common.Hash, key []byte) ([]byte, error)
GetStorageChild(root *common.Hash, keyToChild []byte) (*trie.Trie, error)
GetStorageByBlockHash(bhash common.Hash, key []byte) ([]byte, error)
Entries(root *common.Hash) (map[string][]byte, error)
GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error)
Expand Down
70 changes: 70 additions & 0 deletions dot/rpc/modules/childstate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 <http://www.gnu.org/licenses/>.

package modules

import (
"net/http"

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

// GetKeysRequest represents the request to retrieve the keys of a child storage
type GetKeysRequest struct {
Key []byte
Prefix []byte
Hash common.Hash
}

// ChildStateModule is the module responsible to implement all the childstate RPC calls
type ChildStateModule struct {
storageAPI StorageAPI
blockAPI BlockAPI
}

// NewChildStateModule returns a new ChildStateModule
func NewChildStateModule(s StorageAPI, b BlockAPI) *ChildStateModule {
return &ChildStateModule{
storageAPI: s,
blockAPI: b,
}
}

// GetKeys returns the keys from the specified child storage. The keys can also be filtered based on a prefix.
func (cs *ChildStateModule) GetKeys(_ *http.Request, req *GetKeysRequest, res *[]string) error {
if req.Hash == common.EmptyHash {
req.Hash = cs.blockAPI.BestBlockHash()
}

stateRoot, err := cs.storageAPI.GetStateRootFromBlock(&req.Hash)
if err != nil {
return err
}

trie, err := cs.storageAPI.GetStorageChild(stateRoot, req.Key)
if err != nil {
return err
}

keys := trie.GetKeysWithPrefix(req.Prefix)
hexKeys := make([]string, len(keys))
for idx, k := range keys {
hexKeys[idx] = common.BytesToHex(k)
}

*res = hexKeys
return nil
}
112 changes: 112 additions & 0 deletions dot/rpc/modules/childstate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 <http://www.gnu.org/licenses/>.

package modules

import (
"math/big"
"testing"

"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/stretchr/testify/require"
)

func TestChildStateGetKeys(t *testing.T) {
childStateModule, currBlockHash := setupChildStateStorage(t)

req := &GetKeysRequest{
Key: []byte(":child_storage_key"),
Prefix: []byte{},
Hash: common.EmptyHash,
}

res := make([]string, 0)
err := childStateModule.GetKeys(nil, req, &res)
require.NoError(t, err)
require.Len(t, res, 3)

for _, r := range res {
b, dErr := common.HexToBytes(r)
require.NoError(t, dErr)
require.Contains(t, []string{
":child_first", ":child_second", ":another_child",
}, string(b))
}

req = &GetKeysRequest{
Key: []byte(":child_storage_key"),
Prefix: []byte(":child_"),
Hash: currBlockHash,
}

err = childStateModule.GetKeys(nil, req, &res)
require.NoError(t, err)
require.Len(t, res, 2)

for _, r := range res {
b, err := common.HexToBytes(r)
require.NoError(t, err)
require.Contains(t, []string{
":child_first", ":child_second",
}, string(b))
}
}

func setupChildStateStorage(t *testing.T) (*ChildStateModule, common.Hash) {
t.Helper()

st := newTestStateService(t)

tr, err := st.Storage.TrieState(nil)
require.NoError(t, err)

tr.Set([]byte(":first_key"), []byte(":value1"))
tr.Set([]byte(":second_key"), []byte(":second_value"))

childTr := trie.NewEmptyTrie()
childTr.Put([]byte(":child_first"), []byte(":child_first_value"))
childTr.Put([]byte(":child_second"), []byte(":child_second_value"))
childTr.Put([]byte(":another_child"), []byte("value"))

err = tr.SetChild([]byte(":child_storage_key"), childTr)
require.NoError(t, err)

stateRoot, err := tr.Root()
require.NoError(t, err)

bb, err := st.Block.BestBlock()
require.NoError(t, err)

err = st.Storage.StoreTrie(tr, nil)
require.NoError(t, err)

b := &types.Block{
Header: types.Header{
ParentHash: bb.Header.Hash(),
Number: big.NewInt(0).Add(big.NewInt(1), bb.Header.Number),
StateRoot: stateRoot,
},
Body: []byte{},
}

err = st.Block.AddBlock(b)
require.NoError(t, err)

hash, _ := st.Block.GetBlockHash(b.Header.Number)
return NewChildStateModule(st.Storage, st.Block), hash
}
31 changes: 31 additions & 0 deletions dot/rpc/modules/mocks/rpcapi.go

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

27 changes: 26 additions & 1 deletion dot/rpc/modules/mocks/storage_api.go

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

0 comments on commit c5181ae

Please sign in to comment.