From 00a9376311c94680e83bdd05f5e9d398869a2bd7 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 7 Sep 2022 22:38:54 +0300 Subject: [PATCH] invoker: update documentation, add example --- pkg/rpcclient/invoker/doc_test.go | 99 +++++++++++++++++++++++++++++++ pkg/rpcclient/invoker/invoker.go | 15 ++++- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 pkg/rpcclient/invoker/doc_test.go diff --git a/pkg/rpcclient/invoker/doc_test.go b/pkg/rpcclient/invoker/doc_test.go new file mode 100644 index 000000000..fe01b9490 --- /dev/null +++ b/pkg/rpcclient/invoker/doc_test.go @@ -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 +} diff --git a/pkg/rpcclient/invoker/invoker.go b/pkg/rpcclient/invoker/invoker.go index 266d09687..55a423805 100644 --- a/pkg/rpcclient/invoker/invoker.go +++ b/pkg/rpcclient/invoker/invoker.go @@ -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 import ( @@ -70,6 +79,9 @@ type historicConverter struct { } // 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 { 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 -// 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 { return termSession(v.client, sessionID) }