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/modules) implement state_queryStorage rpc method #1707

Merged
merged 30 commits into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dae9318
wip: state query storage rpc
EclesioMeloJunior Jul 19, 2021
1fa9a96
chore: query keys from block stateRoot
EclesioMeloJunior Jul 26, 2021
49a92d5
chore: implemented rpc method and format response correctly
EclesioMeloJunior Jul 27, 2021
8d02937
chore: resolve conflicts, check value is nil
EclesioMeloJunior Jul 28, 2021
5062c5d
chore: check starting block hash is nil
EclesioMeloJunior Jul 28, 2021
729fb22
chore: use GetStorage instead of TrieState
EclesioMeloJunior Jul 28, 2021
86b1d6f
chore: remove logs
EclesioMeloJunior Jul 28, 2021
3ba79fe
chore: resolve lint issues
EclesioMeloJunior Jul 28, 2021
50a8fba
chore: preallocate slice, use varidic param
EclesioMeloJunior Jul 28, 2021
59189ea
chore: update variable name
EclesioMeloJunior Jul 28, 2021
6cb2465
Merge branch 'development' into eclesio/rpc-state-query-storage
EclesioMeloJunior Aug 1, 2021
91b371c
chore: resolve conflicts
EclesioMeloJunior Aug 3, 2021
08bfddc
Merge branch 'eclesio/rpc-state-query-storage' of github.com:ChainSaf…
EclesioMeloJunior Aug 3, 2021
6e7d83b
chore: made from not nulable
EclesioMeloJunior Aug 3, 2021
ade00e4
chore: get best block hash if to is nil
EclesioMeloJunior Aug 3, 2021
0845fd0
chore: resolve conflicts between development
EclesioMeloJunior Aug 4, 2021
04fdfbe
Merge branch 'development' into eclesio/rpc-state-query-storage
EclesioMeloJunior Aug 4, 2021
ea91284
Merge branch 'development' into eclesio/rpc-state-query-storage
EclesioMeloJunior Aug 5, 2021
5f3f69a
chore: remove pointer and compare with EmptyHash
EclesioMeloJunior Aug 5, 2021
a709595
Merge branch 'development' into eclesio/rpc-state-query-storage
EclesioMeloJunior Aug 6, 2021
0364347
chore: fix tests, remove write to nil reference
EclesioMeloJunior Aug 6, 2021
5002961
Merge branch 'eclesio/rpc-state-query-storage' of github.com:ChainSaf…
EclesioMeloJunior Aug 6, 2021
65d034c
chore: fix deepsource rule
EclesioMeloJunior Aug 6, 2021
0754903
chore: fix rpc test
EclesioMeloJunior Aug 6, 2021
59cbcc2
chore: uncomment conditional test
EclesioMeloJunior Aug 6, 2021
d8047ba
chore: improve testing
EclesioMeloJunior Aug 6, 2021
d46afba
Merge branch 'development' into eclesio/rpc-state-query-storage
EclesioMeloJunior Aug 6, 2021
4f0ac85
chore: change from null to a empty array
EclesioMeloJunior Aug 6, 2021
27b96d3
chore: skip state_queryStorage RPC method test
EclesioMeloJunior Aug 6, 2021
d4264b4
chore: remove unconsitent test check
EclesioMeloJunior Aug 6, 2021
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
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ jobs:
name: Run stable tests
run: |
docker run chainsafe/gossamer:test sh -c "make it-stable"

docker-rpc-tests:
runs-on: ubuntu-latest
steps:
Expand All @@ -155,6 +156,7 @@ jobs:
name: Run rpc tests
run: |
docker run chainsafe/gossamer:test sh -c "make it-rpc"

docker-stress-tests:
runs-on: ubuntu-latest
steps:
Expand All @@ -178,6 +180,7 @@ jobs:
name: Run stress
run: |
docker run chainsafe/gossamer:test sh -c "make it-stress"

docker-grandpa-tests:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions dot/core/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type StorageState interface {
TrieState(root *common.Hash) (*rtstorage.TrieState, error)
StoreTrie(*rtstorage.TrieState, *types.Header) error
GetStateRootFromBlock(bhash *common.Hash) (*common.Hash, error)
GetStorage(root *common.Hash, key []byte) ([]byte, error)
}

// TransactionState is the interface for transaction state methods
Expand Down
58 changes: 58 additions & 0 deletions dot/core/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ var (
logger log.Logger = log.New("pkg", "core")
)

// QueryKeyValueChanges represents the key-value data inside a block storage
type QueryKeyValueChanges map[string]string

// Service is an overhead layer that allows communication between the runtime,
// BABE session, and network service. It deals with the validation of transactions
// and blocks by calling their respective validation functions in the runtime.
Expand Down Expand Up @@ -560,3 +563,58 @@ func (s *Service) GetMetadata(bhash *common.Hash) ([]byte, error) {
rt.SetContextStorage(ts)
return rt.Metadata()
}

// QueryStorage returns the key-value data by block based on `keys` params
// on every block starting `from` until `to` block, if `to` is not nil
func (s *Service) QueryStorage(from, to common.Hash, keys ...string) (map[common.Hash]QueryKeyValueChanges, error) {
if to == common.EmptyHash {
to = s.blockState.BestBlockHash()
}

blocksToQuery, err := s.blockState.SubChain(from, to)
if err != nil {
return nil, err
}

queries := make(map[common.Hash]QueryKeyValueChanges)

for _, hash := range blocksToQuery {
changes, err := s.tryQueryStorage(hash, keys...)
if err != nil {
return nil, err
}

queries[hash] = changes
}

return queries, nil
}

// tryQueryStorage will try to get all the `keys` inside the block's current state
func (s *Service) tryQueryStorage(block common.Hash, keys ...string) (QueryKeyValueChanges, error) {
stateRootHash, err := s.storageState.GetStateRootFromBlock(&block)
if err != nil {
return nil, err
}

changes := make(QueryKeyValueChanges)
for _, k := range keys {
keyBytes, err := common.HexToBytes(k)
if err != nil {
return nil, err
}

storedData, err := s.storageState.GetStorage(stateRootHash, keyBytes)
if err != nil {
return nil, err
}

if storedData == nil {
continue
}

changes[k] = common.BytesToHex(storedData)
}

return changes, nil
}
169 changes: 169 additions & 0 deletions dot/core/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ import (
"github.com/ChainSafe/gossamer/lib/keystore"
"github.com/ChainSafe/gossamer/lib/runtime"
"github.com/ChainSafe/gossamer/lib/runtime/extrinsic"
"github.com/ChainSafe/gossamer/lib/runtime/storage"
"github.com/ChainSafe/gossamer/lib/runtime/wasmer"
"github.com/ChainSafe/gossamer/lib/transaction"
"github.com/ChainSafe/gossamer/lib/trie"
"github.com/ChainSafe/gossamer/lib/utils"
log "github.com/ChainSafe/log15"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -619,6 +621,173 @@ func TestService_HandleRuntimeChangesAfterCodeSubstitutes(t *testing.T) {
require.NotEqualf(t, codeHashBefore, rt.GetCodeHash(), "expected different code hash after runtime update") // codeHash should change after runtime change
}

func TestTryQueryStore_WhenThereIsDataToRetrieve(t *testing.T) {
s := NewTestService(t, nil)
storageStateTrie, err := storage.NewTrieState(trie.NewTrie(nil))

testKey, testValue := []byte("to"), []byte("0x1723712318238AB12312")
storageStateTrie.Set(testKey, testValue)
require.NoError(t, err)

header, err := types.NewHeader(s.blockState.GenesisHash(), storageStateTrie.MustRoot(),
common.Hash{}, big.NewInt(1), nil)
require.NoError(t, err)

err = s.storageState.StoreTrie(storageStateTrie, header)
require.NoError(t, err)

testBlock := &types.Block{
Header: header,
Body: types.NewBody([]byte{}),
}

err = s.blockState.AddBlock(testBlock)
require.NoError(t, err)

blockhash := testBlock.Header.Hash()
hexKey := common.BytesToHex(testKey)
keys := []string{hexKey}

changes, err := s.tryQueryStorage(blockhash, keys...)
require.NoError(t, err)

require.Equal(t, changes[hexKey], common.BytesToHex(testValue))
}

func TestTryQueryStore_WhenDoesNotHaveDataToRetrieve(t *testing.T) {
s := NewTestService(t, nil)
storageStateTrie, err := storage.NewTrieState(trie.NewTrie(nil))
require.NoError(t, err)

header, err := types.NewHeader(s.blockState.GenesisHash(), storageStateTrie.MustRoot(),
common.Hash{}, big.NewInt(1), nil)
require.NoError(t, err)

err = s.storageState.StoreTrie(storageStateTrie, header)
require.NoError(t, err)

testBlock := &types.Block{
Header: header,
Body: types.NewBody([]byte{}),
}

err = s.blockState.AddBlock(testBlock)
require.NoError(t, err)

testKey := []byte("to")
blockhash := testBlock.Header.Hash()
hexKey := common.BytesToHex(testKey)
keys := []string{hexKey}

changes, err := s.tryQueryStorage(blockhash, keys...)
require.NoError(t, err)

require.Empty(t, changes)
}

func TestTryQueryState_WhenDoesNotHaveStateRoot(t *testing.T) {
s := NewTestService(t, nil)

header, err := types.NewHeader(s.blockState.GenesisHash(), common.Hash{}, common.Hash{}, big.NewInt(1), nil)
require.NoError(t, err)

testBlock := &types.Block{
Header: header,
Body: types.NewBody([]byte{}),
}

err = s.blockState.AddBlock(testBlock)
require.NoError(t, err)

testKey := []byte("to")
blockhash := testBlock.Header.Hash()
hexKey := common.BytesToHex(testKey)
keys := []string{hexKey}

changes, err := s.tryQueryStorage(blockhash, keys...)
require.Error(t, err)
require.Nil(t, changes)
}

func TestQueryStorate_WhenBlocksHasData(t *testing.T) {
keys := []string{
common.BytesToHex([]byte("transfer.to")),
common.BytesToHex([]byte("transfer.from")),
common.BytesToHex([]byte("transfer.value")),
}

s := NewTestService(t, nil)

firstKey, firstValue := []byte("transfer.to"), []byte("some-address-herer")
firstBlock := createNewBlockAndStoreDataAtBlock(
t, s, firstKey, firstValue, s.blockState.GenesisHash(), 1,
)

secondKey, secondValue := []byte("transfer.from"), []byte("another-address-here")
secondBlock := createNewBlockAndStoreDataAtBlock(
t, s, secondKey, secondValue, firstBlock.Header.Hash(), 2,
)

thirdKey, thirdValue := []byte("transfer.value"), []byte("value-gigamegablaster")
thirdBlock := createNewBlockAndStoreDataAtBlock(
t, s, thirdKey, thirdValue, secondBlock.Header.Hash(), 3,
)

from := firstBlock.Header.Hash()
data, err := s.QueryStorage(from, common.Hash{}, keys...)
require.NoError(t, err)
require.Len(t, data, 3)

require.Equal(t, data[firstBlock.Header.Hash()], QueryKeyValueChanges(
map[string]string{
common.BytesToHex(firstKey): common.BytesToHex(firstValue),
},
))

from = secondBlock.Header.Hash()
to := thirdBlock.Header.Hash()

data, err = s.QueryStorage(from, to, keys...)
require.NoError(t, err)
require.Len(t, data, 2)

require.Equal(t, data[secondBlock.Header.Hash()], QueryKeyValueChanges(
map[string]string{
common.BytesToHex(secondKey): common.BytesToHex(secondValue),
},
))
require.Equal(t, data[thirdBlock.Header.Hash()], QueryKeyValueChanges(
map[string]string{
common.BytesToHex(thirdKey): common.BytesToHex(thirdValue),
},
))
}

func createNewBlockAndStoreDataAtBlock(t *testing.T, s *Service, key, value []byte, parentHash common.Hash, number int64) *types.Block {
t.Helper()

storageStateTrie, err := storage.NewTrieState(trie.NewTrie(nil))
storageStateTrie.Set(key, value)
require.NoError(t, err)

header, err := types.NewHeader(parentHash, storageStateTrie.MustRoot(),
common.Hash{}, big.NewInt(number), nil)
require.NoError(t, err)

err = s.storageState.StoreTrie(storageStateTrie, header)
require.NoError(t, err)

testBlock := &types.Block{
Header: header,
Body: types.NewBody([]byte{}),
}

err = s.blockState.AddBlock(testBlock)
require.NoError(t, err)

return testBlock
}

func TestDecodeSessionKeys(t *testing.T) {
mockInstance := new(runtimemocks.MockInstance)
mockInstance.On("DecodeSessionKeys", mock.AnythingOfType("[]uint8")).Return([]byte{}, nil).Once()
Expand Down
11 changes: 9 additions & 2 deletions dot/network/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,16 @@ func (s *Service) collectNetworkMetrics() {
}

func (s *Service) logPeerCount() {
ticker := time.NewTicker(time.Second * 30)
defer ticker.Stop()

for {
logger.Debug("peer count", "num", s.host.peerCount(), "min", s.cfg.MinPeers, "max", s.cfg.MaxPeers)
time.Sleep(time.Second * 30)
select {
case <-ticker.C:
logger.Debug("peer count", "num", s.host.peerCount(), "min", s.cfg.MinPeers, "max", s.cfg.MaxPeers)
case <-s.ctx.Done():
return
}
}
}

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 @@ -3,6 +3,7 @@ package modules
import (
"math/big"

"github.com/ChainSafe/gossamer/dot/core"
"github.com/ChainSafe/gossamer/dot/state"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/common"
Expand Down Expand Up @@ -77,6 +78,7 @@ type CoreAPI interface {
GetRuntimeVersion(bhash *common.Hash) (runtime.Version, error)
HandleSubmittedExtrinsic(types.Extrinsic) error
GetMetadata(bhash *common.Hash) ([]byte, error)
QueryStorage(from, to common.Hash, keys ...string) (map[common.Hash]core.QueryKeyValueChanges, error)
DecodeSessionKeys(enc []byte) ([]byte, error)
}

Expand Down
32 changes: 32 additions & 0 deletions dot/rpc/modules/mocks/core_api.go

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

Loading