invoker

package
v0.105.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 12, 2024 License: MIT Imports: 8 Imported by: 14

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

Examples

Constants

View Source
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) Run

func (v *Invoker) Run(script []byte) (*result.Invoke, error)

Run executes given bytecode with Invoker-specific list of signers.

func (*Invoker) TerminateSession added in v0.99.3

func (v *Invoker) TerminateSession(sessionID uuid.UUID) error

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.

func (*Invoker) Verify

func (v *Invoker) Verify(contract util.Uint160, witnesses []transaction.Witness, params ...any) (*result.Invoke, error)

Verify invokes contract's verify method in the verification context with Invoker-specific signers and given witnesses and parameters.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL