diff --git a/pkg/rpcclient/helper.go b/pkg/rpcclient/helper.go index 6f91a5855..10656b289 100644 --- a/pkg/rpcclient/helper.go +++ b/pkg/rpcclient/helper.go @@ -111,6 +111,9 @@ func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) { // retrieve iterator values via single `invokescript` JSON-RPC call. It returns // maxIteratorResultItems items at max which is set to // config.DefaultMaxIteratorResultItems by default. +// +// Deprecated: please use more convenient and powerful invoker.Invoker interface with +// CallAndExpandIterator method. This method will be removed in future versions. func (c *Client) InvokeAndPackIteratorResults(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer, maxIteratorResultItems ...int) (*result.Invoke, error) { max := config.DefaultMaxIteratorResultItems if len(maxIteratorResultItems) != 0 { diff --git a/pkg/rpcclient/invoker/invoker.go b/pkg/rpcclient/invoker/invoker.go index 8336449b4..16a13c972 100644 --- a/pkg/rpcclient/invoker/invoker.go +++ b/pkg/rpcclient/invoker/invoker.go @@ -1,6 +1,8 @@ package invoker import ( + "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -125,6 +127,22 @@ func (v *Invoker) Call(contract util.Uint160, operation string, params ...interf return v.client.InvokeFunction(contract, operation, ps, v.signers) } +// CallAndExpandIterator creates a script containing a call of the specified method +// of a contract with given parameters (similar to how Call operates). But then this +// script contains additional code that expects that the result of the first call is +// an iterator. This iterator is traversed extracting values from it and adding them +// into an array until maxItems is reached or iterator has no more elements. The +// result of the whole script is an array containing up to maxResultItems elements +// from the iterator returned from the contract's method call. This script is executed +// using regular JSON-API (according to the way Iterator is set up). +func (v *Invoker) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) { + bytes, err := smartcontract.CreateCallAndUnwrapIteratorScript(contract, method, maxItems, params...) + if err != nil { + return nil, fmt.Errorf("iterator unwrapper script: %w", err) + } + return v.Run(bytes) +} + // Verify invokes contract's verify method in the verification context with // Invoker-specific signers and given witnesses and parameters. func (v *Invoker) Verify(contract util.Uint160, witnesses []transaction.Witness, params ...interface{}) (*result.Invoke, error) { diff --git a/pkg/rpcclient/invoker/invoker_test.go b/pkg/rpcclient/invoker/invoker_test.go index fd755e421..a5e59af3e 100644 --- a/pkg/rpcclient/invoker/invoker_test.go +++ b/pkg/rpcclient/invoker/invoker_test.go @@ -86,6 +86,13 @@ func TestInvoker(t *testing.T) { _, err = inv.Call(util.Uint160{}, "method", make(map[int]int)) require.Error(t, err) + + res, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, 42) + require.NoError(t, err) + require.Equal(t, resExp, res) + + _, err = inv.CallAndExpandIterator(util.Uint160{}, "method", 10, make(map[int]int)) + require.Error(t, err) } t.Run("standard", func(t *testing.T) { testInv(t, New(ri, nil)) diff --git a/pkg/rpcclient/native.go b/pkg/rpcclient/native.go index 2bbb49e7e..8d1741f43 100644 --- a/pkg/rpcclient/native.go +++ b/pkg/rpcclient/native.go @@ -7,12 +7,12 @@ import ( "fmt" "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nns" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -118,12 +118,7 @@ func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) (uuid.UUID, // that no iterator session is used to retrieve values from iterator. Instead, unpacking // VM script is created and invoked via `invokescript` JSON-RPC call. func (c *Client) NNSUnpackedGetAllRecords(nnsHash util.Uint160, name string) ([]nns.RecordState, error) { - result, err := c.InvokeAndPackIteratorResults(nnsHash, "getAllRecords", []smartcontract.Parameter{ - { - Type: smartcontract.StringType, - Value: name, - }, - }, nil) + result, err := c.reader.CallAndExpandIterator(nnsHash, "getAllRecords", config.DefaultMaxIteratorResultItems, name) if err != nil { return nil, err } diff --git a/pkg/rpcclient/nep11.go b/pkg/rpcclient/nep11.go index f5e908b2b..4865ea78f 100644 --- a/pkg/rpcclient/nep11.go +++ b/pkg/rpcclient/nep11.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" @@ -101,12 +102,7 @@ func (c *Client) NEP11TokensOf(tokenHash util.Uint160, owner util.Uint160) (uuid // is used to retrieve values from iterator. Instead, unpacking VM script is created and invoked via // `invokescript` JSON-RPC call. func (c *Client) NEP11UnpackedTokensOf(tokenHash util.Uint160, owner util.Uint160) ([][]byte, error) { - result, err := c.InvokeAndPackIteratorResults(tokenHash, "tokensOf", []smartcontract.Parameter{ - { - Type: smartcontract.Hash160Type, - Value: owner, - }, - }, nil) + result, err := c.reader.CallAndExpandIterator(tokenHash, "tokensOf", config.DefaultMaxIteratorResultItems, owner) if err != nil { return nil, err } @@ -194,12 +190,7 @@ func (c *Client) NEP11DOwnerOf(tokenHash util.Uint160, tokenID []byte) (uuid.UUI // iterator session is used to retrieve values from iterator. Instead, unpacking VM // script is created and invoked via `invokescript` JSON-RPC call. func (c *Client) NEP11DUnpackedOwnerOf(tokenHash util.Uint160, tokenID []byte) ([]util.Uint160, error) { - result, err := c.InvokeAndPackIteratorResults(tokenHash, "ownerOf", []smartcontract.Parameter{ - { - Type: smartcontract.ByteArrayType, - Value: tokenID, - }, - }, nil) + result, err := c.reader.CallAndExpandIterator(tokenHash, "ownerOf", config.DefaultMaxIteratorResultItems, tokenID) if err != nil { return nil, err } @@ -261,7 +252,7 @@ func (c *Client) NEP11Tokens(tokenHash util.Uint160) (uuid.UUID, result.Iterator // iterator session is used to retrieve values from iterator. Instead, unpacking // VM script is created and invoked via `invokescript` JSON-RPC call. func (c *Client) NEP11UnpackedTokens(tokenHash util.Uint160) ([][]byte, error) { - result, err := c.InvokeAndPackIteratorResults(tokenHash, "tokens", []smartcontract.Parameter{}, nil) + result, err := c.reader.CallAndExpandIterator(tokenHash, "tokens", config.DefaultMaxIteratorResultItems) if err != nil { return nil, err } diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 4c12cf9d1..a53ce077f 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1280,7 +1280,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) { require.NoError(t, err) t.Run("default max items constraint", func(t *testing.T) { - res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil) + res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil) //nolint:staticcheck // SA1019: c.InvokeAndPackIteratorResults is deprecated require.NoError(t, err) require.Equal(t, vmstate.Halt.String(), res.State) require.Equal(t, 1, len(res.Stack)) @@ -1296,7 +1296,7 @@ func TestClient_InvokeAndPackIteratorResults(t *testing.T) { }) t.Run("custom max items constraint", func(t *testing.T) { max := 123 - res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil, max) + res, err := c.InvokeAndPackIteratorResults(storageHash, "iterateOverValues", []smartcontract.Parameter{}, nil, max) //nolint:staticcheck // SA1019: c.InvokeAndPackIteratorResults is deprecated require.NoError(t, err) require.Equal(t, vmstate.Halt.String(), res.State) require.Equal(t, 1, len(res.Stack))