github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/actor/actor.go (about)

     1  /*
     2  Package actor provides a way to change chain state via RPC client.
     3  
     4  This layer builds on top of the basic RPC client and [invoker] package, it
     5  simplifies creating, signing and sending transactions to the network (since
     6  that's the only way chain state is changed). It's generic enough to be used for
     7  any contract that you may want to invoke and contract-specific functions can
     8  build on top of it.
     9  */
    10  package actor
    11  
    12  import (
    13  	"errors"
    14  	"fmt"
    15  
    16  	"github.com/nspcc-dev/neo-go/pkg/config/netmode"
    17  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    18  	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
    19  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
    20  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
    21  	"github.com/nspcc-dev/neo-go/pkg/util"
    22  	"github.com/nspcc-dev/neo-go/pkg/wallet"
    23  )
    24  
    25  // RPCActor is an interface required from the RPC client to successfully
    26  // create and send transactions.
    27  type RPCActor interface {
    28  	invoker.RPCInvoke
    29  
    30  	// CalculateNetworkFee calculates network fee for the given transaction.
    31  	//
    32  	// CalculateNetworkFee MUST NOT call state-changing methods (like Hash or Size)
    33  	// of the transaction through the passed pointer: make a copy if necessary.
    34  	CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
    35  	GetBlockCount() (uint32, error)
    36  	GetVersion() (*result.Version, error)
    37  	SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error)
    38  }
    39  
    40  // SignerAccount represents combination of the transaction.Signer and the
    41  // corresponding wallet.Account. It's used to create and sign transactions, each
    42  // transaction has a set of signers that must witness the transaction with their
    43  // signatures.
    44  type SignerAccount struct {
    45  	Signer  transaction.Signer
    46  	Account *wallet.Account
    47  }
    48  
    49  // Actor keeps a connection to the RPC endpoint and allows to perform
    50  // state-changing actions (via transactions that can also be created without
    51  // sending them to the network) on behalf of a set of signers. It also provides
    52  // an Invoker interface to perform test calls with the same set of signers.
    53  //
    54  // Actor-specific APIs follow the naming scheme set by Invoker in method
    55  // suffixes. *Call methods operate with function calls and require a contract
    56  // hash, a method and parameters if any. *Run methods operate with scripts and
    57  // require a NeoVM script that will be used directly. Prefixes denote the
    58  // action to be performed, "Make" prefix is used for methods that create
    59  // transactions in various ways, while "Send" prefix is used by methods that
    60  // directly transmit created transactions to the RPC server.
    61  //
    62  // Actor also provides a [waiter.Waiter] interface to wait until transaction will be
    63  // accepted to the chain. Depending on the underlying RPCActor functionality,
    64  // transaction awaiting can be performed via web-socket using RPC notifications
    65  // subsystem with [waiter.EventBased], via regular RPC requests using a poll-based
    66  // algorithm with [waiter.PollingBased] or can not be performed if RPCActor doesn't
    67  // implement none of [waiter.RPCEventBased] and [waiter.RPCPollingBased] interfaces with
    68  // [waiter.Null]. [waiter.ErrAwaitingNotSupported] will be returned on attempt to await the
    69  // transaction in the latter case. [waiter.Waiter] uses context of the underlying RPCActor
    70  // and interrupts transaction awaiting process if the context is done.
    71  // [waiter.ErrContextDone] wrapped with the context's error will be returned in this case.
    72  // Otherwise, transaction awaiting process is ended with ValidUntilBlock acceptance
    73  // and [waiter.ErrTxNotAccepted] is returned if transaction wasn't accepted by this moment.
    74  type Actor struct {
    75  	invoker.Invoker
    76  	waiter.Waiter
    77  
    78  	client    RPCActor
    79  	opts      Options
    80  	signers   []SignerAccount
    81  	txSigners []transaction.Signer
    82  	version   *result.Version
    83  }
    84  
    85  // Options are used to create Actor with non-standard transaction checkers or
    86  // additional attributes to be applied for all transactions.
    87  type Options struct {
    88  	// Attributes are set as is into every transaction created by Actor,
    89  	// unless they're explicitly set in a method call that accepts
    90  	// attributes (like MakeTuned* or MakeUnsigned*).
    91  	Attributes []transaction.Attribute
    92  	// CheckerModifier is used by any method that creates and signs a
    93  	// transaction inside (some of them provide ways to override this
    94  	// default, some don't).
    95  	CheckerModifier TransactionCheckerModifier
    96  	// Modifier is used only by MakeUncheckedRun to modify transaction
    97  	// before it's signed (other methods that perform test invocations
    98  	// use CheckerModifier). MakeUnsigned* methods do not run it.
    99  	Modifier TransactionModifier
   100  }
   101  
   102  // New creates an Actor instance using the specified RPC interface and the set of
   103  // signers with corresponding accounts. Every transaction created by this Actor
   104  // will have this set of signers and all communication will be performed via this
   105  // RPC. Upon Actor instance creation a GetVersion call is made and the result of
   106  // it is cached forever (and used for internal purposes). The actor will use
   107  // default Options (which can be overridden using NewTuned).
   108  func New(ra RPCActor, signers []SignerAccount) (*Actor, error) {
   109  	if len(signers) < 1 {
   110  		return nil, errors.New("at least one signer (sender) is required")
   111  	}
   112  	invSigners := make([]transaction.Signer, len(signers))
   113  	for i := range signers {
   114  		if signers[i].Account.Contract == nil {
   115  			return nil, fmt.Errorf("empty contract for account %s", signers[i].Account.Address)
   116  		}
   117  		if !signers[i].Account.Contract.Deployed && signers[i].Account.Contract.ScriptHash() != signers[i].Signer.Account {
   118  			return nil, fmt.Errorf("signer account doesn't match script hash for signer %s", signers[i].Account.Address)
   119  		}
   120  
   121  		invSigners[i] = signers[i].Signer
   122  	}
   123  	inv := invoker.New(ra, invSigners)
   124  	version, err := ra.GetVersion()
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return &Actor{
   129  		Invoker:   *inv,
   130  		Waiter:    waiter.New(ra, version),
   131  		client:    ra,
   132  		opts:      NewDefaultOptions(),
   133  		signers:   signers,
   134  		txSigners: invSigners,
   135  		version:   version,
   136  	}, nil
   137  }
   138  
   139  // NewSimple makes it easier to create an Actor for the most widespread case
   140  // when transactions have only one signer that uses CalledByEntry scope. When
   141  // other scopes or multiple signers are needed use New.
   142  func NewSimple(ra RPCActor, acc *wallet.Account) (*Actor, error) {
   143  	return New(ra, []SignerAccount{{
   144  		Signer: transaction.Signer{
   145  			Account: acc.Contract.ScriptHash(),
   146  			Scopes:  transaction.CalledByEntry,
   147  		},
   148  		Account: acc,
   149  	}})
   150  }
   151  
   152  // NewDefaultOptions returns Options that have no attributes and use the default
   153  // TransactionCheckerModifier function (that checks for the invocation result to
   154  // be in HALT state) and TransactionModifier (that does nothing).
   155  func NewDefaultOptions() Options {
   156  	return Options{
   157  		CheckerModifier: DefaultCheckerModifier,
   158  		Modifier:        DefaultModifier,
   159  	}
   160  }
   161  
   162  // NewTuned creates an Actor that will use the specified Options as defaults when
   163  // creating new transactions. If checker/modifier callbacks are not provided
   164  // (nil), then default ones (from NewDefaultOptions) are used.
   165  func NewTuned(ra RPCActor, signers []SignerAccount, opts Options) (*Actor, error) {
   166  	a, err := New(ra, signers)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	a.opts.Attributes = opts.Attributes
   171  	if opts.CheckerModifier != nil {
   172  		a.opts.CheckerModifier = opts.CheckerModifier
   173  	}
   174  	if opts.Modifier != nil {
   175  		a.opts.Modifier = opts.Modifier
   176  	}
   177  	return a, err
   178  }
   179  
   180  // CalculateNetworkFee wraps RPCActor's CalculateNetworkFee, making it available
   181  // to Actor users directly. It returns network fee value for the given
   182  // transaction.
   183  func (a *Actor) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
   184  	return a.client.CalculateNetworkFee(tx)
   185  }
   186  
   187  // GetBlockCount wraps RPCActor's GetBlockCount, making it available to
   188  // Actor users directly. It returns current number of blocks in the chain.
   189  func (a *Actor) GetBlockCount() (uint32, error) {
   190  	return a.client.GetBlockCount()
   191  }
   192  
   193  // GetNetwork is a convenience method that returns the network's magic number.
   194  func (a *Actor) GetNetwork() netmode.Magic {
   195  	return a.version.Protocol.Network
   196  }
   197  
   198  // GetVersion returns version data from the RPC endpoint.
   199  func (a *Actor) GetVersion() result.Version {
   200  	return *a.version
   201  }
   202  
   203  // Send allows to send arbitrary prepared transaction to the network. It returns
   204  // transaction hash and ValidUntilBlock value.
   205  func (a *Actor) Send(tx *transaction.Transaction) (util.Uint256, uint32, error) {
   206  	h, err := a.client.SendRawTransaction(tx)
   207  	return h, tx.ValidUntilBlock, err
   208  }
   209  
   210  // Sign adds signatures to arbitrary transaction using Actor signers wallets.
   211  // Most of the time it shouldn't be used directly since it'll be successful only
   212  // if the transaction is made using the same set of accounts as the one used
   213  // for Actor creation.
   214  func (a *Actor) Sign(tx *transaction.Transaction) error {
   215  	if len(tx.Signers) != len(a.signers) {
   216  		return errors.New("incorrect number of signers in the transaction")
   217  	}
   218  	for i, signer := range a.signers {
   219  		err := signer.Account.SignTx(a.GetNetwork(), tx)
   220  		if err != nil { // then account is non-contract-based and locked, but let's provide more detailed error
   221  			if paramNum := len(signer.Account.Contract.Parameters); paramNum != 0 && signer.Account.Contract.Deployed {
   222  				return fmt.Errorf("failed to add contract-based witness for signer #%d (%s): "+
   223  					"%d parameters must be provided to construct invocation script", i, signer.Account.Address, paramNum)
   224  			}
   225  			return fmt.Errorf("failed to add witness for signer #%d (%s): account should be unlocked to add the signature. "+
   226  				"Store partially-signed transaction and then use 'wallet sign' command to cosign it", i, signer.Account.Address)
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  // SignAndSend signs arbitrary transaction (see also Sign) and sends it to the
   233  // network.
   234  func (a *Actor) SignAndSend(tx *transaction.Transaction) (util.Uint256, uint32, error) {
   235  	return a.sendWrapper(tx, a.Sign(tx))
   236  }
   237  
   238  // sendWrapper simplifies wrapping methods that create transactions.
   239  func (a *Actor) sendWrapper(tx *transaction.Transaction, err error) (util.Uint256, uint32, error) {
   240  	if err != nil {
   241  		return util.Uint256{}, 0, err
   242  	}
   243  	return a.Send(tx)
   244  }
   245  
   246  // SendCall creates a transaction that calls the given method of the given
   247  // contract with the given parameters (see also MakeCall) and sends it to the
   248  // network.
   249  func (a *Actor) SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) {
   250  	return a.sendWrapper(a.MakeCall(contract, method, params...))
   251  }
   252  
   253  // SendTunedCall creates a transaction that calls the given method of the given
   254  // contract with the given parameters (see also MakeTunedCall) and attributes,
   255  // allowing to check for execution results of this call and modify transaction
   256  // before it's signed; this transaction is then sent to the network.
   257  func (a *Actor) SendTunedCall(contract util.Uint160, method string, attrs []transaction.Attribute, txHook TransactionCheckerModifier, params ...any) (util.Uint256, uint32, error) {
   258  	return a.sendWrapper(a.MakeTunedCall(contract, method, attrs, txHook, params...))
   259  }
   260  
   261  // SendRun creates a transaction with the given executable script (see also
   262  // MakeRun) and sends it to the network.
   263  func (a *Actor) SendRun(script []byte) (util.Uint256, uint32, error) {
   264  	return a.sendWrapper(a.MakeRun(script))
   265  }
   266  
   267  // SendTunedRun creates a transaction with the given executable script and
   268  // attributes, allowing to check for execution results of this script and modify
   269  // transaction before it's signed (see also MakeTunedRun). This transaction is
   270  // then sent to the network.
   271  func (a *Actor) SendTunedRun(script []byte, attrs []transaction.Attribute, txHook TransactionCheckerModifier) (util.Uint256, uint32, error) {
   272  	return a.sendWrapper(a.MakeTunedRun(script, attrs, txHook))
   273  }
   274  
   275  // SendUncheckedRun creates a transaction with the given executable script and
   276  // attributes that can use up to sysfee GAS for its execution, allowing to modify
   277  // this transaction before it's signed (see also MakeUncheckedRun). This
   278  // transaction is then sent to the network.
   279  func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (util.Uint256, uint32, error) {
   280  	return a.sendWrapper(a.MakeUncheckedRun(script, sysfee, attrs, txHook))
   281  }
   282  
   283  // Sender return the sender address that will be used in transactions created
   284  // by Actor.
   285  func (a *Actor) Sender() util.Uint160 {
   286  	return a.txSigners[0].Account
   287  }