From e539bd3db8e0fc0a6a96b881e0edf7d9cabe418d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ecl=C3=A9sio=20Junior?= Date: Fri, 1 Oct 2021 16:22:20 -0400 Subject: [PATCH] feat(rpc): Implement `childstate_getStorageHash` RPC call (#1805) * 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 * feat: implement childstate_getStorageHash * chore: fix export comment * chore: update hash to be a pointer * chore: resolve nil pointer --- dot/rpc/modules/api.go | 1 + dot/rpc/modules/chain_test.go | 1 + dot/rpc/modules/childstate.go | 46 +++++++++++++++++-- dot/rpc/modules/childstate_test.go | 67 +++++++++++++++++++++++++++- dot/rpc/modules/mocks/storage_api.go | 23 ++++++++++ 5 files changed, 132 insertions(+), 6 deletions(-) diff --git a/dot/rpc/modules/api.go b/dot/rpc/modules/api.go index 65c4646654..d79d9e5583 100644 --- a/dot/rpc/modules/api.go +++ b/dot/rpc/modules/api.go @@ -19,6 +19,7 @@ import ( type StorageAPI interface { GetStorage(root *common.Hash, key []byte) ([]byte, error) GetStorageChild(root *common.Hash, keyToChild []byte) (*trie.Trie, error) + GetStorageFromChild(root *common.Hash, keyToChild, key []byte) ([]byte, error) GetStorageByBlockHash(bhash common.Hash, key []byte) ([]byte, error) Entries(root *common.Hash) (map[string][]byte, error) GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error) diff --git a/dot/rpc/modules/chain_test.go b/dot/rpc/modules/chain_test.go index 73254f5849..141f7cdb11 100644 --- a/dot/rpc/modules/chain_test.go +++ b/dot/rpc/modules/chain_test.go @@ -332,6 +332,7 @@ func newTestStateService(t *testing.T) *state.Service { stateSrvc.UseMemDB() gen, genTrie, genesisHeader := genesis.NewTestGenesisWithTrieAndHeader(t) + err = stateSrvc.Initialise(gen, genesisHeader, genTrie) require.NoError(t, err) diff --git a/dot/rpc/modules/childstate.go b/dot/rpc/modules/childstate.go index 1dad74b39c..361afe1fea 100644 --- a/dot/rpc/modules/childstate.go +++ b/dot/rpc/modules/childstate.go @@ -26,7 +26,14 @@ import ( type GetKeysRequest struct { Key []byte Prefix []byte - Hash common.Hash + Hash *common.Hash +} + +// GetStorageHash the request to get the entry child storage hash +type GetStorageHash struct { + KeyChild []byte + EntryKey []byte + Hash *common.Hash } // ChildStateModule is the module responsible to implement all the childstate RPC calls @@ -45,11 +52,15 @@ func NewChildStateModule(s StorageAPI, b BlockAPI) *ChildStateModule { // 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() + var hash common.Hash + + if req.Hash == nil { + hash = cs.blockAPI.BestBlockHash() + } else { + hash = *req.Hash } - stateRoot, err := cs.storageAPI.GetStateRootFromBlock(&req.Hash) + stateRoot, err := cs.storageAPI.GetStateRootFromBlock(&hash) if err != nil { return err } @@ -68,3 +79,30 @@ func (cs *ChildStateModule) GetKeys(_ *http.Request, req *GetKeysRequest, res *[ *res = hexKeys return nil } + +// GetStorageHash returns the hash of a child storage entry +func (cs *ChildStateModule) GetStorageHash(_ *http.Request, req *GetStorageHash, res *string) error { + var hash common.Hash + + if req.Hash == nil { + hash = cs.blockAPI.BestBlockHash() + } else { + hash = *req.Hash + } + + stateRoot, err := cs.storageAPI.GetStateRootFromBlock(&hash) + if err != nil { + return err + } + + item, err := cs.storageAPI.GetStorageFromChild(stateRoot, req.KeyChild, req.EntryKey) + if err != nil { + return err + } + + if item != nil { + *res = common.BytesToHash(item).String() + } + + return nil +} diff --git a/dot/rpc/modules/childstate_test.go b/dot/rpc/modules/childstate_test.go index 5ea9885f4e..c46081698f 100644 --- a/dot/rpc/modules/childstate_test.go +++ b/dot/rpc/modules/childstate_test.go @@ -17,9 +17,12 @@ package modules import ( + "fmt" "math/big" "testing" + "github.com/ChainSafe/chaindb" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/trie" @@ -32,7 +35,7 @@ func TestChildStateGetKeys(t *testing.T) { req := &GetKeysRequest{ Key: []byte(":child_storage_key"), Prefix: []byte{}, - Hash: common.EmptyHash, + Hash: nil, } res := make([]string, 0) @@ -51,7 +54,7 @@ func TestChildStateGetKeys(t *testing.T) { req = &GetKeysRequest{ Key: []byte(":child_storage_key"), Prefix: []byte(":child_"), - Hash: currBlockHash, + Hash: &currBlockHash, } err = childStateModule.GetKeys(nil, req, &res) @@ -67,6 +70,66 @@ func TestChildStateGetKeys(t *testing.T) { } } +func TestGetStorageHash(t *testing.T) { + mod, blockHash := setupChildStateStorage(t) + invalidBlockHash := common.BytesToHash([]byte("invalid block hash")) + + tests := []struct { + expect string + err error + hash *common.Hash + keyChild []byte + entry []byte + }{ + { + err: nil, + expect: common.BytesToHash([]byte(":child_first_value")).String(), + hash: nil, + entry: []byte(":child_first"), + keyChild: []byte(":child_storage_key"), + }, + { + err: nil, + expect: common.BytesToHash([]byte(":child_second_value")).String(), + hash: &blockHash, + entry: []byte(":child_second"), + keyChild: []byte(":child_storage_key"), + }, + { + err: fmt.Errorf("child trie does not exist at key %s%s", trie.ChildStorageKeyPrefix, []byte(":not_exist")), + hash: &blockHash, + entry: []byte(":child_second"), + keyChild: []byte(":not_exist"), + }, + { + err: chaindb.ErrKeyNotFound, + hash: &invalidBlockHash, + }, + } + + for _, test := range tests { + var req GetStorageHash + var res string + + req.Hash = test.hash + req.EntryKey = test.entry + req.KeyChild = test.keyChild + + err := mod.GetStorageHash(nil, &req, &res) + + if test.err != nil { + require.Error(t, err) + require.Equal(t, err, test.err) + } else { + require.NoError(t, err) + } + + if test.expect != "" { + require.Equal(t, test.expect, res) + } + } +} + func setupChildStateStorage(t *testing.T) (*ChildStateModule, common.Hash) { t.Helper() diff --git a/dot/rpc/modules/mocks/storage_api.go b/dot/rpc/modules/mocks/storage_api.go index d61fd3e2a1..1135575e89 100644 --- a/dot/rpc/modules/mocks/storage_api.go +++ b/dot/rpc/modules/mocks/storage_api.go @@ -154,6 +154,29 @@ func (_m *MockStorageAPI) GetStorageChild(root *common.Hash, keyToChild []byte) return r0, r1 } +// GetStorageFromChild provides a mock function with given fields: root, keyToChild, key +func (_m *MockStorageAPI) GetStorageFromChild(root *common.Hash, keyToChild []byte, key []byte) ([]byte, error) { + ret := _m.Called(root, keyToChild, key) + + var r0 []byte + if rf, ok := ret.Get(0).(func(*common.Hash, []byte, []byte) []byte); ok { + r0 = rf(root, keyToChild, key) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*common.Hash, []byte, []byte) error); ok { + r1 = rf(root, keyToChild, key) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // RegisterStorageObserver provides a mock function with given fields: observer func (_m *MockStorageAPI) RegisterStorageObserver(observer state.Observer) { _m.Called(observer)