forked from TrueCloudLab/neoneo-go
rpcsrv, rpcclient: support findstorage
and findstoragehistoric
Close #3095 and add the corresponding historic extension. Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
This commit is contained in:
parent
124c3df2ff
commit
617c628c24
11 changed files with 741 additions and 43 deletions
|
@ -66,6 +66,7 @@ ApplicationConfiguration:
|
||||||
EnableCORSWorkaround: false
|
EnableCORSWorkaround: false
|
||||||
SessionEnabled: true
|
SessionEnabled: true
|
||||||
SessionExpirationTime: 2 # enough for tests as they run locally.
|
SessionExpirationTime: 2 # enough for tests as they run locally.
|
||||||
|
MaxFindStoragePageSize: 2 # small value to test server-side paging
|
||||||
Prometheus:
|
Prometheus:
|
||||||
Enabled: false #since it's not useful for unit tests.
|
Enabled: false #since it's not useful for unit tests.
|
||||||
Addresses:
|
Addresses:
|
||||||
|
|
|
@ -202,6 +202,7 @@ RPC:
|
||||||
MaxGasInvoke: 50
|
MaxGasInvoke: 50
|
||||||
MaxIteratorResultItems: 100
|
MaxIteratorResultItems: 100
|
||||||
MaxFindResultItems: 100
|
MaxFindResultItems: 100
|
||||||
|
MaxFindStoragePageSize: 50
|
||||||
MaxNEP11Tokens: 100
|
MaxNEP11Tokens: 100
|
||||||
MaxWebSocketClients: 64
|
MaxWebSocketClients: 64
|
||||||
SessionEnabled: false
|
SessionEnabled: false
|
||||||
|
@ -238,6 +239,7 @@ where:
|
||||||
`n`, only `n` iterations are returned and truncated is true, indicating that
|
`n`, only `n` iterations are returned and truncated is true, indicating that
|
||||||
there is still data to be returned.
|
there is still data to be returned.
|
||||||
- `MaxFindResultItems` - the maximum number of elements for `findstates` response.
|
- `MaxFindResultItems` - the maximum number of elements for `findstates` response.
|
||||||
|
- `MaxFindStoragePageSize` - the maximum number of elements for `findstorage` response per single page.
|
||||||
- `MaxNEP11Tokens` - limit for the number of tokens returned from
|
- `MaxNEP11Tokens` - limit for the number of tokens returned from
|
||||||
`getnep11balances` call.
|
`getnep11balances` call.
|
||||||
- `MaxWebSocketClients` - the maximum simultaneous websocket client connection
|
- `MaxWebSocketClients` - the maximum simultaneous websocket client connection
|
||||||
|
|
|
@ -19,6 +19,9 @@ const (
|
||||||
// DefaultMaxIteratorResultItems is the default upper bound of traversed
|
// DefaultMaxIteratorResultItems is the default upper bound of traversed
|
||||||
// iterator items per JSON-RPC response.
|
// iterator items per JSON-RPC response.
|
||||||
DefaultMaxIteratorResultItems = 100
|
DefaultMaxIteratorResultItems = 100
|
||||||
|
// DefaultMaxFindStorageResultItems is the default maximum number of resulting
|
||||||
|
// contract storage items that can be retrieved by `findstorge` JSON-RPC handler.
|
||||||
|
DefaultMaxFindStorageResultItems = 50
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is the version of the node, set at the build time.
|
// Version is the version of the node, set at the build time.
|
||||||
|
@ -71,9 +74,10 @@ func LoadFile(configPath string) (Config, error) {
|
||||||
PingTimeout: 90 * time.Second,
|
PingTimeout: 90 * time.Second,
|
||||||
},
|
},
|
||||||
RPC: RPC{
|
RPC: RPC{
|
||||||
MaxIteratorResultItems: DefaultMaxIteratorResultItems,
|
MaxIteratorResultItems: DefaultMaxIteratorResultItems,
|
||||||
MaxFindResultItems: 100,
|
MaxFindResultItems: 100,
|
||||||
MaxNEP11Tokens: 100,
|
MaxFindStorageResultItems: DefaultMaxFindStorageResultItems,
|
||||||
|
MaxNEP11Tokens: 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,18 @@ type (
|
||||||
EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"`
|
EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"`
|
||||||
// MaxGasInvoke is the maximum amount of GAS which
|
// MaxGasInvoke is the maximum amount of GAS which
|
||||||
// can be spent during an RPC call.
|
// can be spent during an RPC call.
|
||||||
MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"`
|
MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"`
|
||||||
MaxIteratorResultItems int `yaml:"MaxIteratorResultItems"`
|
MaxIteratorResultItems int `yaml:"MaxIteratorResultItems"`
|
||||||
MaxFindResultItems int `yaml:"MaxFindResultItems"`
|
MaxFindResultItems int `yaml:"MaxFindResultItems"`
|
||||||
MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"`
|
MaxFindStorageResultItems int `yaml:"MaxFindStoragePageSize"`
|
||||||
MaxWebSocketClients int `yaml:"MaxWebSocketClients"`
|
MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"`
|
||||||
SessionEnabled bool `yaml:"SessionEnabled"`
|
MaxWebSocketClients int `yaml:"MaxWebSocketClients"`
|
||||||
SessionExpirationTime int `yaml:"SessionExpirationTime"`
|
SessionEnabled bool `yaml:"SessionEnabled"`
|
||||||
SessionBackedByMPT bool `yaml:"SessionBackedByMPT"`
|
SessionExpirationTime int `yaml:"SessionExpirationTime"`
|
||||||
SessionPoolSize int `yaml:"SessionPoolSize"`
|
SessionBackedByMPT bool `yaml:"SessionBackedByMPT"`
|
||||||
StartWhenSynchronized bool `yaml:"StartWhenSynchronized"`
|
SessionPoolSize int `yaml:"SessionPoolSize"`
|
||||||
TLSConfig TLS `yaml:"TLSConfig"`
|
StartWhenSynchronized bool `yaml:"StartWhenSynchronized"`
|
||||||
|
TLSConfig TLS `yaml:"TLSConfig"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS describes SSL/TLS configuration.
|
// TLS describes SSL/TLS configuration.
|
||||||
|
|
|
@ -2133,6 +2133,11 @@ func (bc *Blockchain) GetStorageItem(id int32, key []byte) state.StorageItem {
|
||||||
return bc.dao.GetStorageItem(id, key)
|
return bc.dao.GetStorageItem(id, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SeekStorage performs seek operation over contract storage.
|
||||||
|
func (bc *Blockchain) SeekStorage(id int32, prefix []byte, cont func(k, v []byte) bool) {
|
||||||
|
bc.dao.Seek(id, storage.SeekRange{Prefix: prefix}, cont)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlock returns a Block by the given hash.
|
// GetBlock returns a Block by the given hash.
|
||||||
func (bc *Blockchain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
func (bc *Blockchain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||||
topBlock := bc.topBlock.Load()
|
topBlock := bc.topBlock.Load()
|
||||||
|
|
10
pkg/neorpc/result/findstorage.go
Normal file
10
pkg/neorpc/result/findstorage.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package result
|
||||||
|
|
||||||
|
// FindStorage represents the result of `findstorage` RPC handler.
|
||||||
|
type FindStorage struct {
|
||||||
|
Results []KeyValue `json:"results"`
|
||||||
|
// Next contains the index of the next subsequent element of the contract storage
|
||||||
|
// that can be retrieved during the next iteration.
|
||||||
|
Next int `json:"next"`
|
||||||
|
Truncated bool `json:"truncated"`
|
||||||
|
}
|
|
@ -559,6 +559,74 @@ func (c *Client) getStorage(params []any) ([]byte, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindStorageByHash returns contract storage items by the given contract hash and prefix.
|
||||||
|
// If `start` index is specified, items starting from `start` index are being returned
|
||||||
|
// (including item located at the start index).
|
||||||
|
func (c *Client) FindStorageByHash(contractHash util.Uint160, prefix []byte, start *int) (result.FindStorage, error) {
|
||||||
|
var params = []any{contractHash.StringLE(), prefix}
|
||||||
|
if start != nil {
|
||||||
|
params = append(params, *start)
|
||||||
|
}
|
||||||
|
return c.findStorage(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindStorageByID returns contract storage items by the given contract ID and prefix.
|
||||||
|
// If `start` index is specified, items starting from `start` index are being returned
|
||||||
|
// (including item located at the start index).
|
||||||
|
func (c *Client) FindStorageByID(contractID int32, prefix []byte, start *int) (result.FindStorage, error) {
|
||||||
|
var params = []any{contractID, prefix}
|
||||||
|
if start != nil {
|
||||||
|
params = append(params, *start)
|
||||||
|
}
|
||||||
|
return c.findStorage(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) findStorage(params []any) (result.FindStorage, error) {
|
||||||
|
var resp result.FindStorage
|
||||||
|
if err := c.performRequest("findstorage", params, &resp); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindStorageByHashHistoric returns historical contract storage items by the given stateroot,
|
||||||
|
// historical contract hash and historical prefix. If `start` index is specified, then items
|
||||||
|
// starting from `start` index are being returned (including item located at the start index).
|
||||||
|
func (c *Client) FindStorageByHashHistoric(stateroot util.Uint256, historicalContractHash util.Uint160, historicalPrefix []byte,
|
||||||
|
start *int) (result.FindStorage, error) {
|
||||||
|
if historicalPrefix == nil {
|
||||||
|
historicalPrefix = []byte{}
|
||||||
|
}
|
||||||
|
var params = []any{stateroot.StringLE(), historicalContractHash.StringLE(), historicalPrefix}
|
||||||
|
if start != nil {
|
||||||
|
params = append(params, start)
|
||||||
|
}
|
||||||
|
return c.findStorageHistoric(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindStorageByIDHistoric returns historical contract storage items by the given stateroot,
|
||||||
|
// historical contract ID and historical prefix. If `start` index is specified, then items
|
||||||
|
// starting from `start` index are being returned (including item located at the start index).
|
||||||
|
func (c *Client) FindStorageByIDHistoric(stateroot util.Uint256, historicalContractID int32, historicalPrefix []byte,
|
||||||
|
start *int) (result.FindStorage, error) {
|
||||||
|
if historicalPrefix == nil {
|
||||||
|
historicalPrefix = []byte{}
|
||||||
|
}
|
||||||
|
var params = []any{stateroot.StringLE(), historicalContractID, historicalPrefix}
|
||||||
|
if start != nil {
|
||||||
|
params = append(params, start)
|
||||||
|
}
|
||||||
|
return c.findStorageHistoric(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) findStorageHistoric(params []any) (result.FindStorage, error) {
|
||||||
|
var resp result.FindStorage
|
||||||
|
if err := c.performRequest("findstoragehistoric", params, &resp); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetTransactionHeight returns the block index where the transaction is found.
|
// GetTransactionHeight returns the block index where the transaction is found.
|
||||||
func (c *Client) GetTransactionHeight(hash util.Uint256) (uint32, error) {
|
func (c *Client) GetTransactionHeight(hash util.Uint256) (uint32, error) {
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -915,6 +915,74 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"findstorage": {
|
||||||
|
{
|
||||||
|
name: "positive by hash",
|
||||||
|
invoke: func(c *Client) (any, error) {
|
||||||
|
cHash, _ := util.Uint160DecodeStringLE("5c9e40a12055c6b9e3f72271c9779958c842135d")
|
||||||
|
start := 1
|
||||||
|
return c.FindStorageByHash(cHash, []byte("aa"), &start)
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"results":[{"key":"YWExMA==","value":"djI="}],"truncated":true, "next": 1}}`,
|
||||||
|
result: func(c *Client) any {
|
||||||
|
return result.FindStorage{
|
||||||
|
Results: []result.KeyValue{{Key: []byte("aa10"), Value: []byte("v2")}},
|
||||||
|
Truncated: true,
|
||||||
|
Next: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive by ID",
|
||||||
|
invoke: func(c *Client) (any, error) {
|
||||||
|
start := 1
|
||||||
|
return c.FindStorageByID(1, []byte("aa"), &start)
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"results":[{"key":"YWExMA==","value":"djI="}],"truncated":true, "next": 1}}`,
|
||||||
|
result: func(c *Client) any {
|
||||||
|
return result.FindStorage{
|
||||||
|
Results: []result.KeyValue{{Key: []byte("aa10"), Value: []byte("v2")}},
|
||||||
|
Truncated: true,
|
||||||
|
Next: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"findstoragehistoric": {
|
||||||
|
{
|
||||||
|
name: "positive by hash",
|
||||||
|
invoke: func(c *Client) (any, error) {
|
||||||
|
root, _ := util.Uint256DecodeStringLE("252e9d73d49c95c7618d40650da504e05183a1b2eed0685e42c360413c329170")
|
||||||
|
cHash, _ := util.Uint160DecodeStringLE("5c9e40a12055c6b9e3f72271c9779958c842135d")
|
||||||
|
start := 1
|
||||||
|
return c.FindStorageByHashHistoric(root, cHash, []byte("aa"), &start)
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"results":[{"key":"YWExMA==","value":"djI="}],"truncated":true, "next": 1}}`,
|
||||||
|
result: func(c *Client) any {
|
||||||
|
return result.FindStorage{
|
||||||
|
Results: []result.KeyValue{{Key: []byte("aa10"), Value: []byte("v2")}},
|
||||||
|
Truncated: true,
|
||||||
|
Next: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive by ID",
|
||||||
|
invoke: func(c *Client) (any, error) {
|
||||||
|
root, _ := util.Uint256DecodeStringLE("252e9d73d49c95c7618d40650da504e05183a1b2eed0685e42c360413c329170")
|
||||||
|
start := 1
|
||||||
|
return c.FindStorageByIDHistoric(root, 1, []byte("aa"), &start)
|
||||||
|
},
|
||||||
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"results":[{"key":"YWExMA==","value":"djI="}],"truncated":true, "next": 1}}`,
|
||||||
|
result: func(c *Client) any {
|
||||||
|
return result.FindStorage{
|
||||||
|
Results: []result.KeyValue{{Key: []byte("aa10"), Value: []byte("v2")}},
|
||||||
|
Truncated: true,
|
||||||
|
Next: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"getstateheight": {
|
"getstateheight": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
|
@ -2562,3 +2562,131 @@ func TestActor_CallWithNilParam(t *testing.T) {
|
||||||
|
|
||||||
require.True(t, strings.Contains(res.FaultException, "invalid conversion: Null/ByteString"), res.FaultException)
|
require.True(t, strings.Contains(res.FaultException, "invalid conversion: Null/ByteString"), res.FaultException)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClient_FindStorage(t *testing.T) {
|
||||||
|
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
defer rpcSrv.Shutdown()
|
||||||
|
|
||||||
|
c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, c.Init())
|
||||||
|
|
||||||
|
h, err := util.Uint160DecodeStringLE(testContractHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prefix := []byte("aa")
|
||||||
|
expected := result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa"),
|
||||||
|
Value: []byte("v1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"),
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 2,
|
||||||
|
Truncated: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// By hash.
|
||||||
|
actual, err := c.FindStorageByHash(h, prefix, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
|
||||||
|
// By ID.
|
||||||
|
actual, err = c.FindStorageByID(1, prefix, nil) // Rubles contract
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
|
||||||
|
// Non-nil start.
|
||||||
|
start := 1
|
||||||
|
actual, err = c.FindStorageByHash(h, prefix, &start)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"),
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte("aa50"),
|
||||||
|
Value: []byte("v3"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 3,
|
||||||
|
Truncated: false,
|
||||||
|
}, actual)
|
||||||
|
|
||||||
|
// Missing item.
|
||||||
|
actual, err = c.FindStorageByHash(h, []byte("unknown prefix"), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, result.FindStorage{}, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClient_FindStorageHistoric(t *testing.T) {
|
||||||
|
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
defer rpcSrv.Shutdown()
|
||||||
|
|
||||||
|
c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, c.Init())
|
||||||
|
|
||||||
|
root, err := util.Uint256DecodeStringLE(block20StateRootLE)
|
||||||
|
require.NoError(t, err)
|
||||||
|
h, err := util.Uint160DecodeStringLE(testContractHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prefix := []byte("aa")
|
||||||
|
expected := result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"),
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte("aa50"),
|
||||||
|
Value: []byte("v3"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 2,
|
||||||
|
Truncated: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// By hash.
|
||||||
|
actual, err := c.FindStorageByHashHistoric(root, h, prefix, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
|
||||||
|
// By ID.
|
||||||
|
actual, err = c.FindStorageByIDHistoric(root, 1, prefix, nil) // Rubles contract
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
|
||||||
|
// Non-nil start.
|
||||||
|
start := 1
|
||||||
|
actual, err = c.FindStorageByHashHistoric(root, h, prefix, &start)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa50"),
|
||||||
|
Value: []byte("v3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte("aa"), // order differs due to MPT traversal strategy.
|
||||||
|
Value: []byte("v1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 3,
|
||||||
|
Truncated: false,
|
||||||
|
}, actual)
|
||||||
|
|
||||||
|
// Missing item.
|
||||||
|
earlyRoot, err := chain.GetStateRoot(15) // there's no `aa10` value in Rubles contract by the moment of block #15
|
||||||
|
require.NoError(t, err)
|
||||||
|
actual, err = c.FindStorageByHashHistoric(earlyRoot.Root, h, prefix, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, result.FindStorage{}, actual)
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
@ -98,6 +99,7 @@ type (
|
||||||
GetValidators() ([]*keys.PublicKey, error)
|
GetValidators() ([]*keys.PublicKey, error)
|
||||||
HeaderHeight() uint32
|
HeaderHeight() uint32
|
||||||
InitVerificationContext(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error
|
InitVerificationContext(ic *interop.Context, hash util.Uint160, witness *transaction.Witness) error
|
||||||
|
SeekStorage(id int32, prefix []byte, cont func(k, v []byte) bool)
|
||||||
SubscribeForBlocks(ch chan *block.Block)
|
SubscribeForBlocks(ch chan *block.Block)
|
||||||
SubscribeForExecutions(ch chan *state.AppExecResult)
|
SubscribeForExecutions(ch chan *state.AppExecResult)
|
||||||
SubscribeForNotifications(ch chan *state.ContainedNotificationEvent)
|
SubscribeForNotifications(ch chan *state.ContainedNotificationEvent)
|
||||||
|
@ -199,6 +201,8 @@ const (
|
||||||
var rpcHandlers = map[string]func(*Server, params.Params) (any, *neorpc.Error){
|
var rpcHandlers = map[string]func(*Server, params.Params) (any, *neorpc.Error){
|
||||||
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
||||||
"findstates": (*Server).findStates,
|
"findstates": (*Server).findStates,
|
||||||
|
"findstorage": (*Server).findStorage,
|
||||||
|
"findstoragehistoric": (*Server).findStorageHistoric,
|
||||||
"getapplicationlog": (*Server).getApplicationLog,
|
"getapplicationlog": (*Server).getApplicationLog,
|
||||||
"getbestblockhash": (*Server).getBestBlockHash,
|
"getbestblockhash": (*Server).getBestBlockHash,
|
||||||
"getblock": (*Server).getBlock,
|
"getblock": (*Server).getBlock,
|
||||||
|
@ -1414,17 +1418,25 @@ func (s *Server) getHash(contractID int32, cache map[int32]util.Uint160) (util.U
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) contractIDFromParam(param *params.Param) (int32, *neorpc.Error) {
|
func (s *Server) contractIDFromParam(param *params.Param, root ...util.Uint256) (int32, *neorpc.Error) {
|
||||||
var result int32
|
var result int32
|
||||||
if param == nil {
|
if param == nil {
|
||||||
return 0, neorpc.ErrInvalidParams
|
return 0, neorpc.ErrInvalidParams
|
||||||
}
|
}
|
||||||
if scriptHash, err := param.GetUint160FromHex(); err == nil {
|
if scriptHash, err := param.GetUint160FromHex(); err == nil {
|
||||||
cs := s.chain.GetContractState(scriptHash)
|
if len(root) == 0 {
|
||||||
if cs == nil {
|
cs := s.chain.GetContractState(scriptHash)
|
||||||
return 0, neorpc.ErrUnknownContract
|
if cs == nil {
|
||||||
|
return 0, neorpc.ErrUnknownContract
|
||||||
|
}
|
||||||
|
result = cs.ID
|
||||||
|
} else {
|
||||||
|
cs, respErr := s.getHistoricalContractState(root[0], scriptHash)
|
||||||
|
if respErr != nil {
|
||||||
|
return 0, respErr
|
||||||
|
}
|
||||||
|
result = cs.ID
|
||||||
}
|
}
|
||||||
result = cs.ID
|
|
||||||
} else {
|
} else {
|
||||||
id, err := param.GetInt()
|
id, err := param.GetInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1539,18 +1551,9 @@ func (s *Server) verifyProof(ps params.Params) (any, *neorpc.Error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getState(ps params.Params) (any, *neorpc.Error) {
|
func (s *Server) getState(ps params.Params) (any, *neorpc.Error) {
|
||||||
root, err := ps.Value(0).GetUint256()
|
root, respErr := s.getStateRootFromParam(ps.Value(0))
|
||||||
if err != nil {
|
if respErr != nil {
|
||||||
return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, "invalid stateroot")
|
return nil, respErr
|
||||||
}
|
|
||||||
if s.chain.GetConfig().Ledger.KeepOnlyLatestState {
|
|
||||||
curr, err := s.chain.GetStateModule().GetStateRoot(s.chain.BlockHeight())
|
|
||||||
if err != nil {
|
|
||||||
return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to get current stateroot: %s", err))
|
|
||||||
}
|
|
||||||
if !curr.Root.Equals(root) {
|
|
||||||
return nil, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("'getstate' is not supported for old states: %s", errKeepOnlyLatestState))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
csHash, err := ps.Value(1).GetUint160FromHex()
|
csHash, err := ps.Value(1).GetUint160FromHex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1576,18 +1579,9 @@ func (s *Server) getState(ps params.Params) (any, *neorpc.Error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) findStates(ps params.Params) (any, *neorpc.Error) {
|
func (s *Server) findStates(ps params.Params) (any, *neorpc.Error) {
|
||||||
root, err := ps.Value(0).GetUint256()
|
root, respErr := s.getStateRootFromParam(ps.Value(0))
|
||||||
if err != nil {
|
if respErr != nil {
|
||||||
return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, "invalid stateroot")
|
return nil, respErr
|
||||||
}
|
|
||||||
if s.chain.GetConfig().Ledger.KeepOnlyLatestState {
|
|
||||||
curr, err := s.chain.GetStateModule().GetStateRoot(s.chain.BlockHeight())
|
|
||||||
if err != nil {
|
|
||||||
return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to get current stateroot: %s", err))
|
|
||||||
}
|
|
||||||
if !curr.Root.Equals(root) {
|
|
||||||
return nil, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("'findstates' is not supported for old states: %s", errKeepOnlyLatestState))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
csHash, err := ps.Value(1).GetUint160FromHex()
|
csHash, err := ps.Value(1).GetUint160FromHex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1669,6 +1663,127 @@ func (s *Server) findStates(ps params.Params) (any, *neorpc.Error) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getStateRootFromParam retrieves state root hash from the provided parameter
|
||||||
|
// (only util.Uint256 serialized representation is allowed) and checks whether
|
||||||
|
// MPT states are supported for the old stateroot.
|
||||||
|
func (s *Server) getStateRootFromParam(p *params.Param) (util.Uint256, *neorpc.Error) {
|
||||||
|
root, err := p.GetUint256()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, "invalid stateroot")
|
||||||
|
}
|
||||||
|
if s.chain.GetConfig().Ledger.KeepOnlyLatestState {
|
||||||
|
curr, err := s.chain.GetStateModule().GetStateRoot(s.chain.BlockHeight())
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, neorpc.NewInternalServerError(fmt.Sprintf("failed to get current stateroot: %s", err))
|
||||||
|
}
|
||||||
|
if !curr.Root.Equals(root) {
|
||||||
|
return util.Uint256{}, neorpc.WrapErrorWithData(neorpc.ErrUnsupportedState, fmt.Sprintf("state-based methods are not supported for old states: %s", errKeepOnlyLatestState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) findStorage(reqParams params.Params) (any, *neorpc.Error) {
|
||||||
|
id, prefix, start, take, respErr := s.getFindStorageParams(reqParams)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
i int
|
||||||
|
end = start + take
|
||||||
|
res = new(result.FindStorage)
|
||||||
|
)
|
||||||
|
s.chain.SeekStorage(id, prefix, func(k, v []byte) bool {
|
||||||
|
if i < start {
|
||||||
|
i++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if i < end {
|
||||||
|
res.Results = append(res.Results, result.KeyValue{
|
||||||
|
Key: slice.Copy(append(prefix, k...)), // Don't strip prefix, as it is done in C#.
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
i++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
res.Truncated = true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
res.Next = i
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) findStorageHistoric(reqParams params.Params) (any, *neorpc.Error) {
|
||||||
|
root, respErr := s.getStateRootFromParam(reqParams.Value(0))
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
if len(reqParams) < 2 {
|
||||||
|
return nil, neorpc.ErrInvalidParams
|
||||||
|
}
|
||||||
|
id, prefix, start, take, respErr := s.getFindStorageParams(reqParams[1:], root)
|
||||||
|
if respErr != nil {
|
||||||
|
return nil, respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
end = start + take
|
||||||
|
res = new(result.FindStorage)
|
||||||
|
pKey = makeStorageKey(id, prefix)
|
||||||
|
)
|
||||||
|
// @roman-khimov, retrieving only the necessary part of the contract storage
|
||||||
|
// requires an mpt Billet refactoring, we can do it in a separate issue, create?
|
||||||
|
kvs, err := s.chain.GetStateModule().FindStates(root, pKey, nil, end+1) // +1 to define result truncation
|
||||||
|
if err != nil && !errors.Is(err, mpt.ErrNotFound) {
|
||||||
|
return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to find state items: %s", err))
|
||||||
|
}
|
||||||
|
if len(kvs) == end+1 {
|
||||||
|
res.Truncated = true
|
||||||
|
kvs = kvs[:len(kvs)-1]
|
||||||
|
}
|
||||||
|
if start >= len(kvs) {
|
||||||
|
kvs = nil
|
||||||
|
} else {
|
||||||
|
kvs = kvs[start:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(kvs) != 0 { // keep consistency with `findstorage` response
|
||||||
|
res.Results = make([]result.KeyValue, len(kvs))
|
||||||
|
for i := range res.Results {
|
||||||
|
res.Results[i] = result.KeyValue{
|
||||||
|
Key: kvs[i].Key[4:], // Cut contract ID as it is done in C#.
|
||||||
|
Value: kvs[i].Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Next = start + len(res.Results)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getFindStorageParams(reqParams params.Params, root ...util.Uint256) (int32, []byte, int, int, *neorpc.Error) {
|
||||||
|
if len(reqParams) < 2 {
|
||||||
|
return 0, nil, 0, 0, neorpc.ErrInvalidParams
|
||||||
|
}
|
||||||
|
id, respErr := s.contractIDFromParam(reqParams.Value(0), root...)
|
||||||
|
if respErr != nil {
|
||||||
|
return 0, nil, 0, 0, respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix, err := reqParams.Value(1).GetBytesBase64()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, 0, 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid prefix: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var skip int
|
||||||
|
if len(reqParams) > 2 {
|
||||||
|
skip, err = reqParams.Value(2).GetInt()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, 0, 0, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid start: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id, prefix, skip, s.config.MaxFindStorageResultItems, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) getHistoricalContractState(root util.Uint256, csHash util.Uint160) (*state.Contract, *neorpc.Error) {
|
func (s *Server) getHistoricalContractState(root util.Uint256, csHash util.Uint160) (*state.Contract, *neorpc.Error) {
|
||||||
csKey := makeStorageKey(native.ManagementContractID, native.MakeContractKey(csHash))
|
csKey := makeStorageKey(native.ManagementContractID, native.MakeContractKey(csHash))
|
||||||
csBytes, err := s.chain.GetStateModule().GetState(root, csKey)
|
csBytes, err := s.chain.GetStateModule().GetState(root, csKey)
|
||||||
|
|
|
@ -128,6 +128,14 @@ var rpcFunctionsWithUnsupportedStatesTestCases = map[string][]rpcTestCase{
|
||||||
errCode: neorpc.ErrUnsupportedStateCode,
|
errCode: neorpc.ErrUnsupportedStateCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"findstoragehistoric": {
|
||||||
|
{
|
||||||
|
name: "unsupported state",
|
||||||
|
params: `["` + block20StateRootLE + `", "0xabcdef"]`,
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.ErrUnsupportedStateCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
"invokefunctionhistoric": {
|
"invokefunctionhistoric": {
|
||||||
{
|
{
|
||||||
name: "unsupported state",
|
name: "unsupported state",
|
||||||
|
@ -651,6 +659,294 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
errCode: neorpc.InvalidParamsCode,
|
errCode: neorpc.InvalidParamsCode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"findstorage": {
|
||||||
|
{
|
||||||
|
name: "not truncated",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s"]`, testContractHash, base64.StdEncoding.EncodeToString([]byte("aa1"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"),
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 1,
|
||||||
|
Truncated: false,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncated first page",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s"]`, testContractHash, base64.StdEncoding.EncodeToString([]byte("aa"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa"),
|
||||||
|
Value: []byte("v1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"),
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 2,
|
||||||
|
Truncated: true,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncated second page",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", 2]`, testContractHash, base64.StdEncoding.EncodeToString([]byte("aa"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa50"),
|
||||||
|
Value: []byte("v3"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 3,
|
||||||
|
Truncated: false,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty prefix",
|
||||||
|
params: fmt.Sprintf(`["%s", ""]`, storageContractHash),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte{0x01, 0x00},
|
||||||
|
Value: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte{0x01, 0x01},
|
||||||
|
Value: []byte{0x01},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 2,
|
||||||
|
Truncated: true,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s"]`, testContractHash, base64.StdEncoding.EncodeToString([]byte("unknown-key"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: nil,
|
||||||
|
Next: 0,
|
||||||
|
Truncated: false,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no second parameter",
|
||||||
|
params: fmt.Sprintf(`["%s"]`, testContractHash),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid hash",
|
||||||
|
params: `["notahex"]`,
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid key",
|
||||||
|
params: fmt.Sprintf(`["%s", "notabase64$"]`, testContractHash),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid page",
|
||||||
|
params: fmt.Sprintf(`["%s", "", "not-an-int"]`, testContractHash),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"findstoragehistoric": {
|
||||||
|
{
|
||||||
|
name: "not truncated",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHash, base64.StdEncoding.EncodeToString([]byte("aa1"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"),
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 1,
|
||||||
|
Truncated: false,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncated first page",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHash, base64.StdEncoding.EncodeToString([]byte("aa"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa10"), // items traversal order may differ from the one provided by `findstorage` due to MPT traversal strategy.
|
||||||
|
Value: []byte("v2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: []byte("aa50"),
|
||||||
|
Value: []byte("v3"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 2,
|
||||||
|
Truncated: true,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truncated second page",
|
||||||
|
params: fmt.Sprintf(`["%s","%s", "%s", 2]`, block20StateRootLE, testContractHash, base64.StdEncoding.EncodeToString([]byte("aa"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte("aa"),
|
||||||
|
Value: []byte("v1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 3,
|
||||||
|
Truncated: false,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty prefix",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", ""]`, block20StateRootLE, nnsContractHash),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{
|
||||||
|
Results: []result.KeyValue{
|
||||||
|
{
|
||||||
|
Key: []byte{0x00}, // total supply
|
||||||
|
Value: []byte{0x01},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: append([]byte{0x01}, testchain.PrivateKeyByID(0).GetScriptHash().BytesBE()...), // balance of priv0
|
||||||
|
Value: []byte{0x01},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Next: 2,
|
||||||
|
Truncated: true,
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown key",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", "%s"]`, block20StateRootLE, testContractHash, base64.StdEncoding.EncodeToString([]byte("unknown-key"))),
|
||||||
|
result: func(_ *executor) any { return new(result.FindStorage) },
|
||||||
|
check: func(t *testing.T, e *executor, res any) {
|
||||||
|
actual, ok := res.(*result.FindStorage)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expected := &result.FindStorage{}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid stateroot",
|
||||||
|
params: `[12345]`,
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no second parameter",
|
||||||
|
params: fmt.Sprintf(`["%s"]`, block20StateRootLE),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no third parameter",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s"]`, block20StateRootLE, testContractHash),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid hash",
|
||||||
|
params: fmt.Sprintf(`["%s", "notahex"]`, block20StateRootLE),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid key",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", "notabase64$"]`, block20StateRootLE, testContractHash),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid page",
|
||||||
|
params: fmt.Sprintf(`["%s", "%s", "", "not-an-int"]`, block20StateRootLE, testContractHash),
|
||||||
|
fail: true,
|
||||||
|
errCode: neorpc.InvalidParamsCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
"getbestblockhash": {
|
"getbestblockhash": {
|
||||||
{
|
{
|
||||||
params: "[]",
|
params: "[]",
|
||||||
|
|
Loading…
Reference in a new issue