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 8 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
55 changes: 55 additions & 0 deletions dot/rpc/modules/childstate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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 {
hex := common.BytesToHex(k)
hexKeys[idx] = hex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hex := common.BytesToHex(k)
hexKeys[idx] = hex
hexKeys[idx] = common.BytesToHex(k)

}

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