Documentation ¶
Overview ¶
Package invoker provides a convenient wrapper to perform test calls via RPC client.
This layer builds on top of the basic RPC client and simplifies performing test function invocations and script runs. It also makes historic calls (NeoGo extension) transparent, allowing to use the same API as for regular calls. Results of these calls can be interpreted by upper layer packages like actor (to create transactions) or unwrap (to retrieve data from return values).
Index ¶
- Constants
- type Invoker
- func (v *Invoker) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
- func (v *Invoker) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error)
- func (v *Invoker) Run(script []byte) (*result.Invoke, error)
- func (v *Invoker) TerminateSession(sessionID uuid.UUID) error
- func (v *Invoker) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
- func (v *Invoker) Verify(contract util.Uint160, witnesses []transaction.Witness, params ...any) (*result.Invoke, error)
- type RPCInvoke
- type RPCInvokeHistoric
- type RPCSessions
Examples ¶
Constants ¶
const DefaultIteratorResultItems = 100
DefaultIteratorResultItems is the default number of results to request from the iterator. Typically it's the same as server's MaxIteratorResultItems, but different servers can have different settings.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Invoker ¶
type Invoker struct {
// contains filtered or unexported fields
}
Invoker allows to test-execute things using RPC client. Its API simplifies reusing the same signers list for a series of invocations and at the same time uses regular Go types for call parameters. It doesn't do anything with the result of invocation, that's left for upper (contract) layer to deal with. Invoker does not produce any transactions and does not change the state of the chain.
Example ¶
package main import ( "context" "errors" "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/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" ) func main() { // No error checking done at all, intentionally. c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{}) // A simple invoker with no signers, perfectly fine for reads from safe methods. inv := invoker.New(c, nil) // Get the NEO token supply (notice that unwrap is used to get the result). supply, _ := unwrap.BigInt(inv.Call(neo.Hash, "totalSupply")) _ = supply acc, _ := address.StringToUint160("NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq") // Get the NEO balance for account NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq. balance, _ := unwrap.BigInt(inv.Call(neo.Hash, "balanceOf", acc)) _ = balance // Test-invoke transfer call. res, _ := inv.Call(neo.Hash, "transfer", acc, util.Uint160{1, 2, 3}, 1, nil) if res.State == vmstate.Halt.String() { panic("NEO is broken!") // inv has no signers and transfer requires a witness to be performed. } else { // nolint:revive // superfluous-else: if block ends with call to panic function, so drop this else and outdent its block (revive) println("ok") // this actually should fail } // A historic invoker with no signers at block 1000000. inv = invoker.NewHistoricAtHeight(1000000, c, nil) // It's the same call as above, but the data is for a state at block 1000000. balance, _ = unwrap.BigInt(inv.Call(neo.Hash, "balanceOf", acc)) _ = balance // This invoker has a signer for NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq account with // CalledByEntry scope, which is sufficient for most operation. It uses current // state which is exactly what you need if you want to then create a transaction // with the same action. inv = invoker.New(c, []transaction.Signer{{Account: acc, Scopes: transaction.CalledByEntry}}) // Now test invocation should be fine (if NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq has 1 NEO of course). res, _ = inv.Call(neo.Hash, "transfer", acc, util.Uint160{1, 2, 3}, 1, nil) if res.State == vmstate.Halt.String() { // transfer actually returns a value, so check it too. ok, _ := unwrap.Bool(res, nil) if ok { // OK, as expected. // res.Script contains the corresponding script. _ = res.Script // res.GasConsumed has an appropriate system fee required for a transaction. _ = res.GasConsumed } } // Now let's try working with iterators. nep11Contract := util.Uint160{1, 2, 3} var tokens [][]byte // Try doing it the right way, by traversing the iterator. sess, iter, err := unwrap.SessionIterator(inv.Call(nep11Contract, "tokensOf", acc)) // The server doesn't support sessions and doesn't perform iterator expansion, // iterators can't be used. if err != nil { if errors.Is(err, unwrap.ErrNoSessionID) { // But if we expect some low number of elements, CallAndExpandIterator // can help us in this case. If the account has more than 10 elements, // some of them will be missing from the response. tokens, _ = unwrap.ArrayOfBytes(inv.CallAndExpandIterator(nep11Contract, "tokensOf", 10, acc)) } else { panic("some error") } } else { items, err := inv.TraverseIterator(sess, &iter, 100) // Keep going until there are no more elements for err == nil && len(items) != 0 { for _, itm := range items { tokenID, _ := itm.TryBytes() tokens = append(tokens, tokenID) } items, err = inv.TraverseIterator(sess, &iter, 100) } // Let the server release the session. _ = inv.TerminateSession(sess) } _ = tokens }
Output:
func New ¶
func New(client RPCInvoke, signers []transaction.Signer) *Invoker
New creates an Invoker to test-execute things at the current blockchain height. If you only want to read data from the contract using its safe methods normally (but contract-specific in general case) it's OK to pass nil for signers (that is, use no signers).
func NewHistoricAtHeight ¶
func NewHistoricAtHeight(height uint32, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker
NewHistoricAtHeight creates an Invoker to test-execute things at some given height.
func NewHistoricWithState ¶
func NewHistoricWithState(rootOrBlock util.Uint256, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker
NewHistoricWithState creates an Invoker to test-execute things with some given state or block.
func (*Invoker) Call ¶
func (v *Invoker) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
Call invokes a method of the contract with the given parameters (and Invoker-specific list of signers) and returns the result as is.
func (*Invoker) CallAndExpandIterator ¶
func (v *Invoker) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error)
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 (*Invoker) TerminateSession ¶ added in v0.99.3
TerminateSession closes the given session, returning an error if anything goes wrong. It's not strictly required to close the session (it'll expire on the server anyway), but it helps to release server resources earlier.
func (*Invoker) TraverseIterator ¶ added in v0.99.3
func (v *Invoker) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
TraverseIterator allows to retrieve the next batch of items from the given iterator in the given session (previously returned from Call or Run). It works both with session-backed iterators and expanded ones (which one you have depends on the RPC server). It can change the state of the iterator in the process. If num <= 0 then DefaultIteratorResultItems number of elements is requested. If result contains no elements, then either Iterator has no elements or session was expired and terminated by the server.
type RPCInvoke ¶
type RPCInvoke interface { RPCSessions InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) }
RPCInvoke is a set of RPC methods needed to execute things at the current blockchain height.
type RPCInvokeHistoric ¶
type RPCInvokeHistoric interface { RPCSessions InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) }
RPCInvokeHistoric is a set of RPC methods needed to execute things at some fixed point in blockchain's life.
type RPCSessions ¶ added in v0.99.3
type RPCSessions interface { TerminateSession(sessionID uuid.UUID) (bool, error) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) }
RPCSessions is a set of RPC methods needed to retrieve values from the session-based iterators.