actor: extend documentation, add example

This commit is contained in:
Roman Khimov 2022-09-07 15:11:27 +03:00
parent e1fe76137e
commit cb1a1f8532
2 changed files with 135 additions and 4 deletions

View file

@ -1,10 +1,11 @@
/* /*
Package actor provides a way to change chain state via RPC client. Package actor provides a way to change chain state via RPC client.
This layer builds on top of the basic RPC client and simplifies creating, This layer builds on top of the basic RPC client and [invoker] package, it
signing and sending transactions to the network (since that's the only way chain simplifies creating, signing and sending transactions to the network (since
state is changed). It's generic enough to be used for any contract that you may that's the only way chain state is changed). It's generic enough to be used for
want to invoke and contract-specific functions can build on top of it. any contract that you may want to invoke and contract-specific functions can
build on top of it.
*/ */
package actor package actor
@ -44,6 +45,14 @@ type SignerAccount struct {
// state-changing actions (via transactions that can also be created without // state-changing actions (via transactions that can also be created without
// sending them to the network) on behalf of a set of signers. It also provides // sending them to the network) on behalf of a set of signers. It also provides
// an Invoker interface to perform test calls with the same set of signers. // an Invoker interface to perform test calls with the same set of signers.
//
// Actor-specific APIs follow the naming scheme set by Invoker in method
// suffixes. *Call methods operate with function calls and require a contract
// hash, a method and parameters if any. *Run methods operate with scripts and
// require a NeoVM script that will be used directly. Prefixes denote the
// action to be performed, "Make" prefix is used for methods that create
// transactions in various ways, while "Send" prefix is used by methods that
// directly transmit created transactions to the RPC server.
type Actor struct { type Actor struct {
invoker.Invoker invoker.Invoker

View file

@ -0,0 +1,122 @@
package actor_test
import (
"context"
"encoding/json"
"os"
"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/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
sccontext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
func ExampleActor() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Create a simple CalledByEntry-scoped actor (assuming there are accounts
// inside the wallet).
a, _ := actor.NewSimple(c, w.Accounts[0])
customContract := util.Uint160{9, 8, 7}
// Actor has an Invoker inside, so we can perform test invocations, it will
// have a signer with the first wallet account and CalledByEntry scope.
res, _ := a.Call(customContract, "method", 1, 2, 3)
if res.State != vmstate.Halt.String() {
// The call failed.
}
// All of the side-effects in res can be analyzed.
// Now we want to send the same invocation in a transaction, but we already
// have the script and a proper system fee for it, therefore SendUncheckedRun
// can be used.
txid, vub, _ := a.SendUncheckedRun(res.Script, res.GasConsumed, nil, nil)
_ = txid
_ = vub
// You need to wait for it to persist and then check the on-chain result of it.
// Now we want to send some transaction, but give it a priority by increasing
// its network fee, this can be done with Tuned APIs.
txid, vub, _ = a.SendTunedCall(customContract, "method", nil, func(r *result.Invoke, t *transaction.Transaction) error {
// This code is run after the test-invocation done by *Call methods.
// Reuse the default function to check for HALT execution state.
err := actor.DefaultCheckerModifier(r, t)
if err != nil {
return err
}
// Some additional checks can be performed right here, but we only
// want to raise the network fee by ~20%.
t.NetworkFee += (t.NetworkFee / 5)
return nil
}, 1, 2, 3)
_ = txid
_ = vub
// Actor can be used for higher-level wrappers as well, if we want to interact with
// NEO then [neo] package can accept our Actor and allow to easily use NEO methods.
neoContract := neo.New(a)
balance, _ := neoContract.BalanceOf(a.Sender())
_ = balance
// Now suppose the second wallet account is a committee account. We want to
// create and sign transactions for committee, but use the first account as
// a sender (because committee account has no GAS). We at the same time want
// to make all transactions using this actor high-priority ones, because
// committee can use this attribute.
// Get the default options to have CheckerModifier/Modifier set up correctly.
opts := actor.NewDefaultOptions()
// And override attributes.
opts.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}}
// Create an Actor.
a, _ = actor.NewTuned(c, []actor.SignerAccount{{
// Sender, regular account with None scope.
Signer: transaction.Signer{
Account: w.Accounts[0].ScriptHash(),
Scopes: transaction.None,
},
Account: w.Accounts[0],
}, {
// Commmitee.
Signer: transaction.Signer{
Account: w.Accounts[1].ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: w.Accounts[1],
}}, opts)
// Use policy contract wrapper to simplify things. All changes in the
// Policy contract are made by the committee.
policyContract := policy.New(a)
// Create a transaction to set storage price, it'll be high-priority and have two
// signers from above. Committee is a multisignature account, so we can't sign/send
// it right away, w.Accounts[1] has only one public key. Therefore, we need to
// create a partially signed transaction and save it, then collect other signatures
// and send.
tx, _ := policyContract.SetStoragePriceUnsigned(10)
net := a.GetNetwork()
scCtx := sccontext.NewParameterContext("Neo.Network.P2P.Payloads.Transaction", net, tx)
sign := w.Accounts[0].SignHashable(net, tx)
_ = scCtx.AddSignature(w.Accounts[0].ScriptHash(), w.Accounts[0].Contract, w.Accounts[0].PublicKey(), sign)
sign = w.Accounts[1].SignHashable(net, tx)
_ = scCtx.AddSignature(w.Accounts[1].ScriptHash(), w.Accounts[1].Contract, w.Accounts[1].PublicKey(), sign)
data, _ := json.Marshal(scCtx)
_ = os.WriteFile("tx.json", data, 0644)
// Signature collection is out of scope, usually it's manual for cases like this.
}