Skip to content

Commit

Permalink
feat(dot/rpc): implement RPC method state_queryStorageAt (#3191)
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardmack authored Apr 12, 2023
1 parent af0435d commit 3bbdfe0
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 6 deletions.
43 changes: 43 additions & 0 deletions dot/rpc/modules/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ type StateStorageQueryRangeRequest struct {
EndBlock common.Hash `json:"block"`
}

// StateStorageQueryAtRequest holds json fields
type StateStorageQueryAtRequest struct {
Keys []string `json:"keys" validate:"required"`
At common.Hash `json:"at"`
}

// StateStorageKeysQuery field to store storage keys
type StateStorageKeysQuery [][]byte

Expand Down Expand Up @@ -467,10 +473,13 @@ func (sm *StateModule) QueryStorage(
var hexValue *string
if len(value) > 0 {
hexValue = stringPtr(common.BytesToHex(value))
} else if value != nil { // empty byte slice value
hexValue = stringPtr("0x")
}

differentValueEncountered := i == startBlockNumber ||
lastValue[j] == nil && hexValue != nil ||
lastValue[j] != nil && hexValue == nil ||
lastValue[j] != nil && *lastValue[j] != *hexValue
if differentValueEncountered {
changes = append(changes, [2]*string{stringPtr(key), hexValue})
Expand All @@ -489,6 +498,40 @@ func (sm *StateModule) QueryStorage(
return nil
}

// QueryStorageAt queries historical storage entries (by key) at the block hash given or
// the best block if the given block hash is nil
func (sm *StateModule) QueryStorageAt(
_ *http.Request, request *StateStorageQueryAtRequest, response *[]StorageChangeSetResponse) error {
atBlockHash := request.At
if atBlockHash.IsEmpty() {
atBlockHash = sm.blockAPI.BestBlockHash()
}

changes := make([][2]*string, len(request.Keys))

for i, key := range request.Keys {
value, err := sm.storageAPI.GetStorageByBlockHash(&atBlockHash, common.MustHexToBytes(key))
if err != nil {
return fmt.Errorf("getting value by block hash: %w", err)
}
var hexValue *string
if len(value) > 0 {
hexValue = stringPtr(common.BytesToHex(value))
} else if value != nil { // empty byte slice value
hexValue = stringPtr("0x")
}

changes[i] = [2]*string{stringPtr(key), hexValue}
}

*response = []StorageChangeSetResponse{{
Block: &atBlockHash,
Changes: changes,
}}

return nil
}

func stringPtr(s string) *string { return &s }

// SubscribeRuntimeVersion initialised a runtime version subscription and returns the current version
Expand Down
148 changes: 142 additions & 6 deletions dot/rpc/modules/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,11 +936,11 @@ func TestStateModuleQueryStorage(t *testing.T) {
storageAPIBuilder: func(ctrl *gomock.Controller) StorageAPI {
mockStorageAPI := NewMockStorageAPI(ctrl)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{1}, []byte{1, 2, 4}).
Return([]byte{}, nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{1, 2, 4}).
Return([]byte{1, 1, 1}, nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{1, 2, 4}).
Return([]byte(nil), nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{3}, []byte{1, 2, 4}).
Return([]byte{2, 2, 2}, nil)
Return([]byte{}, nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{4}, []byte{1, 2, 4}).
Return([]byte{3, 3, 3}, nil)
return mockStorageAPI
Expand Down Expand Up @@ -968,19 +968,19 @@ func TestStateModuleQueryStorage(t *testing.T) {
{
Block: &common.Hash{1},
Changes: [][2]*string{
{stringPtr("0x010204"), nil},
makeChange("0x010204", "0x010101"),
},
},
{
Block: &common.Hash{2},
Changes: [][2]*string{
makeChange("0x010204", "0x010101"),
{stringPtr("0x010204"), nil},
},
},
{
Block: &common.Hash{3},
Changes: [][2]*string{
makeChange("0x010204", "0x020202"),
makeChange("0x010204", "0x"),
},
},
{
Expand Down Expand Up @@ -1129,3 +1129,139 @@ func TestStateModuleQueryStorage(t *testing.T) {
})
}
}
func TestStateModuleQueryStorageAt(t *testing.T) {
t.Parallel()
errTest := errors.New("test error")

type fields struct {
storageAPIBuilder func(ctrl *gomock.Controller) *MockStorageAPI
blockAPIBuilder func(ctrl *gomock.Controller) *MockBlockAPI
}

tests := map[string]struct {
fields fields
request *StateStorageQueryAtRequest
expectedError error
expectedResponse []StorageChangeSetResponse
}{
"missing_start_block": {
fields: fields{
storageAPIBuilder: func(ctrl *gomock.Controller) *MockStorageAPI {
mockStorageAPI := NewMockStorageAPI(ctrl)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{1, 2, 3}).
Return([]byte{1, 1, 1}, nil)
return mockStorageAPI
},
blockAPIBuilder: func(ctrl *gomock.Controller) *MockBlockAPI {
mockBlockAPI := NewMockBlockAPI(ctrl)
mockBlockAPI.EXPECT().BestBlockHash().Return(common.Hash{2})
return mockBlockAPI
}},
request: &StateStorageQueryAtRequest{
Keys: []string{"0x010203"},
},
expectedResponse: []StorageChangeSetResponse{
{
Block: &common.Hash{2},
Changes: [][2]*string{
makeChange("0x010203", "0x010101"),
},
},
},
},
"start_block_not_found_error": {
fields: fields{
storageAPIBuilder: func(ctrl *gomock.Controller) *MockStorageAPI {
mockStorageAPI := NewMockStorageAPI(ctrl)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{1}, []byte{1, 2, 3}).Return(nil, errTest)
return mockStorageAPI
},
blockAPIBuilder: func(ctrl *gomock.Controller) *MockBlockAPI {
return NewMockBlockAPI(ctrl)
}},
request: &StateStorageQueryAtRequest{
Keys: []string{"0x010203"},
At: common.Hash{1},
},
expectedResponse: []StorageChangeSetResponse{},
expectedError: errors.New("getting value by block hash: test error"),
},
"start_block/multi_keys": {
fields: fields{
storageAPIBuilder: func(ctrl *gomock.Controller) *MockStorageAPI {
mockStorageAPI := NewMockStorageAPI(ctrl)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{8, 8, 8}).
Return([]byte{8, 8, 8, 8}, nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{1, 2, 4}).
Return([]byte{}, nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{9, 9, 9}).
Return([]byte(nil), nil)
return mockStorageAPI
},
blockAPIBuilder: func(ctrl *gomock.Controller) *MockBlockAPI {
return NewMockBlockAPI(ctrl)
}},
request: &StateStorageQueryAtRequest{
Keys: []string{"0x080808", "0x010204", "0x090909"},
At: common.Hash{2},
},
expectedResponse: []StorageChangeSetResponse{
{
Block: &common.Hash{2},
Changes: [][2]*string{
makeChange("0x080808", "0x08080808"),
makeChange("0x010204", "0x"),
{stringPtr("0x090909"), nil},
},
},
},
},
"missing_start_block/multi_keys": {
fields: fields{
storageAPIBuilder: func(ctrl *gomock.Controller) *MockStorageAPI {
mockStorageAPI := NewMockStorageAPI(ctrl)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{1, 2, 4}).
Return([]byte{1, 1, 1}, nil)
mockStorageAPI.EXPECT().GetStorageByBlockHash(&common.Hash{2}, []byte{9, 9, 9}).
Return([]byte{9, 9, 9, 9}, nil)
return mockStorageAPI
},
blockAPIBuilder: func(ctrl *gomock.Controller) *MockBlockAPI {
mockBlockAPI := NewMockBlockAPI(ctrl)
mockBlockAPI.EXPECT().BestBlockHash().Return(common.Hash{2})
return mockBlockAPI
}},
request: &StateStorageQueryAtRequest{
Keys: []string{"0x010204", "0x090909"},
},
expectedResponse: []StorageChangeSetResponse{
{
Block: &common.Hash{2},
Changes: [][2]*string{
makeChange("0x010204", "0x010101"),
makeChange("0x090909", "0x09090909"),
},
},
},
},
}
for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
sm := &StateModule{
storageAPI: tt.fields.storageAPIBuilder(ctrl),
blockAPI: tt.fields.blockAPIBuilder(ctrl),
}
response := []StorageChangeSetResponse{}
err := sm.QueryStorageAt(nil, tt.request, &response)
if tt.expectedError != nil {
assert.EqualError(t, err, tt.expectedError.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResponse, response)
})
}
}

0 comments on commit 3bbdfe0

Please sign in to comment.