Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dot/rpc): Implement childstate_getKeys rpc call #1800

Merged
merged 14 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from 10 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: 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
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
}
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.