invoker: update documentation, add example

This commit is contained in:
Roman Khimov 2022-09-07 22:38:54 +03:00
parent ea92f3d716
commit 00a9376311
2 changed files with 113 additions and 1 deletions

View file

@ -0,0 +1,99 @@
package invoker_test
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 ExampleInvoker() {
// 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() {
// NEO is broken! inv has no signers and transfer requires a witness to be performed.
} else {
// 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 and res.GasConsumed has an appropriate system fee
// required for a transaction.
}
}
// 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
}

View file

@ -1,3 +1,12 @@
/*
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).
*/
package invoker package invoker
import ( import (
@ -70,6 +79,9 @@ type historicConverter struct {
} }
// New creates an Invoker to test-execute things at the current blockchain height. // 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 New(client RPCInvoke, signers []transaction.Signer) *Invoker { func New(client RPCInvoke, signers []transaction.Signer) *Invoker {
return &Invoker{client, signers} return &Invoker{client, signers}
} }
@ -187,7 +199,8 @@ func (v *Invoker) Run(script []byte) (*result.Invoke, error) {
} }
// TerminateSession closes the given session, returning an error if anything // TerminateSession closes the given session, returning an error if anything
// goes wrong. // 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 (v *Invoker) TerminateSession(sessionID uuid.UUID) error { func (v *Invoker) TerminateSession(sessionID uuid.UUID) error {
return termSession(v.client, sessionID) return termSession(v.client, sessionID)
} }