Merge pull request #2632 from nspcc-dev/rpcclient-actor

RPC client Actor interface
This commit is contained in:
Roman Khimov 2022-08-09 17:21:24 +03:00 committed by GitHub
commit 593fa4cac8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1037 additions and 77 deletions

View file

@ -12,14 +12,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
) )
// validUntilBlockIncrement is the number of extra blocks to add to an exported transaction.
const validUntilBlockIncrement = 50
// InitAndSave creates an incompletely signed transaction which can be used // InitAndSave creates an incompletely signed transaction which can be used
// as an input to `multisig sign`. // as an input to `multisig sign`.
func InitAndSave(net netmode.Magic, tx *transaction.Transaction, acc *wallet.Account, filename string) error { func InitAndSave(net netmode.Magic, tx *transaction.Transaction, acc *wallet.Account, filename string) error {
// avoid fast transaction expiration
tx.ValidUntilBlock += validUntilBlockIncrement
priv := acc.PrivateKey() priv := acc.PrivateKey()
pub := priv.PublicKey() pub := priv.PublicKey()
sign := priv.SignHashable(uint32(net), tx) sign := priv.SignHashable(uint32(net), tx)

View file

@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/cmdargs"
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
@ -23,6 +24,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" "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"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
@ -663,6 +665,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
resp *result.Invoke resp *result.Invoke
sender util.Uint160 sender util.Uint160
signAndPush = acc != nil signAndPush = acc != nil
act *actor.Actor
) )
if signAndPush { if signAndPush {
gas = flags.Fixed8FromContext(ctx, "gas") gas = flags.Fixed8FromContext(ctx, "gas")
@ -683,75 +686,101 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
if err != nil { if err != nil {
return sender, err return sender, err
} }
if signAndPush {
// This will eventually be handled in cmdargs.GetSignersAccounts.
asa := make([]actor.SignerAccount, 0, len(cosigners)+1)
asa = append(asa, actor.SignerAccount{
Signer: transaction.Signer{
Account: sender,
Scopes: transaction.None,
},
Account: acc,
})
for _, c := range cosignersAccounts {
if c.Signer.Account == sender {
asa[0].Signer = c.Signer
continue
}
asa = append(asa, actor.SignerAccount{
Signer: c.Signer,
Account: c.Account,
})
}
act, err = actor.New(c, asa)
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to create RPC actor: %w", err), 1)
}
}
out := ctx.String("out") out := ctx.String("out")
// It's a bit easier to keep this as is (not using invoker.Invoker)
// during transition period. Mostly because of the need to convert params
// to []interface{}.
resp, err = c.InvokeFunction(script, operation, params, cosigners) resp, err = c.InvokeFunction(script, operation, params, cosigners)
if err != nil { if err != nil {
return sender, cli.NewExitError(err, 1) return sender, cli.NewExitError(err, 1)
} }
if resp.State != "HALT" { if resp.State != "HALT" {
errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException) errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException)
if out == "" && !signAndPush { if !signAndPush {
return sender, cli.NewExitError(errText, 1) return sender, cli.NewExitError(errText, 1)
} }
action := "save" action := "save"
process := "Saving" process := "Saving"
if signAndPush { if out != "" {
if out != "" { action += "and send"
action += "and send" process += "and sending"
process += "and sending" } else {
} else { action = "send"
action = "send" process = "Sending"
process = "Sending"
}
} }
if !ctx.Bool("force") { if !ctx.Bool("force") {
return sender, cli.NewExitError(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1) return sender, cli.NewExitError(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1)
} }
fmt.Fprintln(ctx.App.Writer, errText+".\n"+process+" transaction...") fmt.Fprintln(ctx.App.Writer, errText+".\n"+process+" transaction...")
} }
if out != "" { if !signAndPush {
tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed+int64(sysgas), int64(gas), cosignersAccounts)
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
}
m, err := c.GetNetwork()
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1)
}
if err := paramcontext.InitAndSave(m, tx, acc, out); err != nil {
return sender, cli.NewExitError(err, 1)
}
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
return sender, nil
}
if signAndPush {
if len(resp.Script) == 0 {
return sender, cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
}
tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed+int64(sysgas), int64(gas), cosignersAccounts)
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
}
if !ctx.Bool("force") {
err := input.ConfirmTx(ctx.App.Writer, tx)
if err != nil {
return sender, cli.NewExitError(err, 1)
}
}
txHash, err := c.SignAndPushTx(tx, acc, cosignersAccounts)
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
}
fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE())
} else {
b, err := json.MarshalIndent(resp, "", " ") b, err := json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {
return sender, cli.NewExitError(err, 1) return sender, cli.NewExitError(err, 1)
} }
fmt.Fprintln(ctx.App.Writer, string(b)) fmt.Fprintln(ctx.App.Writer, string(b))
} else {
if len(resp.Script) == 0 {
return sender, cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
}
ver := act.GetVersion()
tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed+int64(sysgas), nil)
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
}
tx.NetworkFee += int64(gas)
if out != "" {
// Make a long-lived transaction, it's to be signed manually.
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
m := act.GetNetwork()
if err := paramcontext.InitAndSave(m, tx, acc, out); err != nil {
return sender, cli.NewExitError(err, 1)
}
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
} else {
if !ctx.Bool("force") {
promptTime := time.Now()
err := input.ConfirmTx(ctx.App.Writer, tx)
if err != nil {
return sender, cli.NewExitError(err, 1)
}
waitTime := time.Since(promptTime)
// Compensate for confirmation waiting.
tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1
}
txHash, _, err := act.SignAndSend(tx)
if err != nil {
return sender, cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
}
fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE())
}
} }
return sender, nil return sender, nil

View file

@ -280,6 +280,12 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet
tx.SystemFee += int64(sysgas) tx.SystemFee += int64(sysgas)
if outFile := ctx.String("out"); outFile != "" { if outFile := ctx.String("out"); outFile != "" {
ver, err := c.GetVersion()
if err != nil {
return cli.NewExitError(fmt.Errorf("RPC failure: %w", err), 1)
}
// Make a long-lived transaction, it's to be signed manually.
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
m, err := c.GetNetwork() m, err := c.GetNetwork()
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1)
@ -294,7 +300,7 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
} }
_, err := c.SignAndPushTx(tx, acc, cosigners) _, err := c.SignAndPushTx(tx, acc, cosigners) //nolint:staticcheck // SA1019: c.SignAndPushTx is deprecated
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }

View file

@ -659,6 +659,12 @@ func signAndSendNEP17Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet
tx.SystemFee += int64(sysgas) tx.SystemFee += int64(sysgas)
if outFile := ctx.String("out"); outFile != "" { if outFile := ctx.String("out"); outFile != "" {
ver, err := c.GetVersion()
if err != nil {
return cli.NewExitError(fmt.Errorf("RPC failure: %w", err), 1)
}
// Make a long-lived transaction, it's to be signed manually.
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
m, err := c.GetNetwork() m, err := c.GetNetwork()
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to save tx: %w", err), 1)
@ -673,7 +679,7 @@ func signAndSendNEP17Transfer(ctx *cli.Context, c *rpcclient.Client, acc *wallet
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
} }
_, err := c.SignAndPushTx(tx, acc, cosigners) _, err := c.SignAndPushTx(tx, acc, cosigners) //nolint:staticcheck // SA1019: c.SignAndPushTx is deprecated
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }

View file

@ -127,7 +127,7 @@ func handleCandidate(ctx *cli.Context, method string, sysGas int64) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
res, err := c.SignAndPushInvocationTx(script, acc, sysGas, gas, []rpcclient.SignerAccount{{ res, err := c.SignAndPushInvocationTx(script, acc, sysGas, gas, []rpcclient.SignerAccount{{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(), Account: acc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry, Scopes: transaction.CalledByEntry,
@ -191,7 +191,7 @@ func handleVote(ctx *cli.Context) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
res, err := c.SignAndPushInvocationTx(script, acc, -1, gas, []rpcclient.SignerAccount{{ res, err := c.SignAndPushInvocationTx(script, acc, -1, gas, []rpcclient.SignerAccount{{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(), Account: acc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry, Scopes: transaction.CalledByEntry,

View file

@ -162,6 +162,15 @@ latest state synchronization point P the node working against,
`LastUpdatedBlock` equals P. For NEP-11 NFTs `LastUpdatedBlock` is equal for `LastUpdatedBlock` equals P. For NEP-11 NFTs `LastUpdatedBlock` is equal for
all tokens of the same asset. all tokens of the same asset.
##### `getversion`
NeoGo can return additional fields in the `protocol` object depending on the
extensions enabled. Specifically that's `p2psigextensions` and
`staterootinheader` booleans and `committeehistory` and `validatorshistory`
objects (that are effectively maps from stringified integers to other
integers. These fields are only returned when corresponding settings are
enabled in the server's protocol configuration.
##### `getnep11transfers` and `getnep17transfers` ##### `getnep11transfers` and `getnep17transfers`
`transfernotifyindex` is not tracked by NeoGo, thus this field is always zero. `transfernotifyindex` is not tracked by NeoGo, thus this field is always zero.

View file

@ -39,8 +39,18 @@ type (
MemoryPoolMaxTransactions int MemoryPoolMaxTransactions int
ValidatorsCount byte ValidatorsCount byte
InitialGasDistribution fixedn.Fixed8 InitialGasDistribution fixedn.Fixed8
// Below are NeoGo-specific extensions to the protocol that are
// returned by the server in case they're enabled.
// CommitteeHistory stores height:size map of the committee size.
CommitteeHistory map[uint32]int
// P2PSigExtensions is true when Notary subsystem is enabled on the network.
P2PSigExtensions bool
// StateRootInHeader is true if state root is contained in block header. // StateRootInHeader is true if state root is contained in block header.
StateRootInHeader bool StateRootInHeader bool
// ValidatorsHistory stores height:size map of the validators count.
ValidatorsHistory map[uint32]int
} }
) )
@ -67,7 +77,11 @@ type (
MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"` MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"`
ValidatorsCount byte `json:"validatorscount"` ValidatorsCount byte `json:"validatorscount"`
InitialGasDistribution int64 `json:"initialgasdistribution"` InitialGasDistribution int64 `json:"initialgasdistribution"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
CommitteeHistory map[uint32]int `json:"committeehistory,omitempty"`
P2PSigExtensions bool `json:"p2psigextensions,omitempty"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
ValidatorsHistory map[uint32]int `json:"validatorshistory,omitempty"`
} }
// versionUnmarshallerAux is an auxiliary struct used for Version JSON unmarshalling. // versionUnmarshallerAux is an auxiliary struct used for Version JSON unmarshalling.
@ -92,7 +106,11 @@ type (
MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"` MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"`
ValidatorsCount byte `json:"validatorscount"` ValidatorsCount byte `json:"validatorscount"`
InitialGasDistribution json.RawMessage `json:"initialgasdistribution"` InitialGasDistribution json.RawMessage `json:"initialgasdistribution"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
CommitteeHistory map[uint32]int `json:"committeehistory,omitempty"`
P2PSigExtensions bool `json:"p2psigextensions,omitempty"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
ValidatorsHistory map[uint32]int `json:"validatorshistory,omitempty"`
} }
) )
@ -118,7 +136,11 @@ func (v *Version) MarshalJSON() ([]byte, error) {
MemoryPoolMaxTransactions: v.Protocol.MemoryPoolMaxTransactions, MemoryPoolMaxTransactions: v.Protocol.MemoryPoolMaxTransactions,
ValidatorsCount: v.Protocol.ValidatorsCount, ValidatorsCount: v.Protocol.ValidatorsCount,
InitialGasDistribution: int64(v.Protocol.InitialGasDistribution), InitialGasDistribution: int64(v.Protocol.InitialGasDistribution),
StateRootInHeader: v.Protocol.StateRootInHeader,
CommitteeHistory: v.Protocol.CommitteeHistory,
P2PSigExtensions: v.Protocol.P2PSigExtensions,
StateRootInHeader: v.Protocol.StateRootInHeader,
ValidatorsHistory: v.Protocol.ValidatorsHistory,
}, },
StateRootInHeader: v.StateRootInHeader, StateRootInHeader: v.StateRootInHeader,
} }
@ -145,7 +167,10 @@ func (v *Version) UnmarshalJSON(data []byte) error {
v.Protocol.MaxTransactionsPerBlock = aux.Protocol.MaxTransactionsPerBlock v.Protocol.MaxTransactionsPerBlock = aux.Protocol.MaxTransactionsPerBlock
v.Protocol.MemoryPoolMaxTransactions = aux.Protocol.MemoryPoolMaxTransactions v.Protocol.MemoryPoolMaxTransactions = aux.Protocol.MemoryPoolMaxTransactions
v.Protocol.ValidatorsCount = aux.Protocol.ValidatorsCount v.Protocol.ValidatorsCount = aux.Protocol.ValidatorsCount
v.Protocol.CommitteeHistory = aux.Protocol.CommitteeHistory
v.Protocol.P2PSigExtensions = aux.Protocol.P2PSigExtensions
v.Protocol.StateRootInHeader = aux.Protocol.StateRootInHeader v.Protocol.StateRootInHeader = aux.Protocol.StateRootInHeader
v.Protocol.ValidatorsHistory = aux.Protocol.ValidatorsHistory
v.StateRootInHeader = aux.StateRootInHeader v.StateRootInHeader = aux.StateRootInHeader
if len(aux.Protocol.InitialGasDistribution) == 0 { if len(aux.Protocol.InitialGasDistribution) == 0 {
return nil return nil

View file

@ -0,0 +1,204 @@
/*
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,
signing and sending transactions to the network (since that's the only way chain
state is changed). It's generic enough to be used for any contract that you may
want to invoke and contract-specific functions can build on top of it.
*/
package actor
import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"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/invoker"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
// RPCActor is an interface required from the RPC client to successfully
// create and send transactions.
type RPCActor interface {
invoker.RPCInvoke
CalculateNetworkFee(tx *transaction.Transaction) (int64, error)
GetBlockCount() (uint32, error)
GetVersion() (*result.Version, error)
SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error)
}
// SignerAccount represents combination of the transaction.Signer and the
// corresponding wallet.Account. It's used to create and sign transactions, each
// transaction has a set of signers that must witness the transaction with their
// signatures.
type SignerAccount struct {
Signer transaction.Signer
Account *wallet.Account
}
// Actor keeps a connection to the RPC endpoint and allows to perform
// 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
// an Invoker interface to perform test calls with the same set of signers.
type Actor struct {
invoker.Invoker
client RPCActor
signers []SignerAccount
txSigners []transaction.Signer
version *result.Version
}
// New creates an Actor instance using the specified RPC interface and the set of
// signers with corresponding accounts. Every transaction created by this Actor
// will have this set of signers and all communication will be performed via this
// RPC. Upon Actor instance creation a GetVersion call is made and the result of
// it is cached forever (and used for internal purposes).
func New(ra RPCActor, signers []SignerAccount) (*Actor, error) {
if len(signers) < 1 {
return nil, errors.New("at least one signer (sender) is required")
}
invSigners := make([]transaction.Signer, len(signers))
for i := range signers {
if signers[i].Account.Contract == nil {
return nil, fmt.Errorf("empty contract for account %s", signers[i].Account.Address)
}
if !signers[i].Account.Contract.Deployed && signers[i].Account.Contract.ScriptHash() != signers[i].Signer.Account {
return nil, fmt.Errorf("signer account doesn't match script hash for signer %s", signers[i].Account.Address)
}
invSigners[i] = signers[i].Signer
}
inv := invoker.New(ra, invSigners)
version, err := ra.GetVersion()
if err != nil {
return nil, err
}
return &Actor{
Invoker: *inv,
client: ra,
signers: signers,
txSigners: invSigners,
version: version,
}, nil
}
// NewSimple makes it easier to create an Actor for the most widespread case
// when transactions have only one signer that uses CalledByEntry scope. When
// other scopes or multiple signers are needed use New.
func NewSimple(ra RPCActor, acc *wallet.Account) (*Actor, error) {
return New(ra, []SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: acc,
}})
}
// CalculateNetworkFee wraps RPCActor's CalculateNetworkFee, making it available
// to Actor users directly. It returns network fee value for the given
// transaction.
func (a *Actor) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
return a.client.CalculateNetworkFee(tx)
}
// GetBlockCount wraps RPCActor's GetBlockCount, making it available to
// Actor users directly. It returns current number of blocks in the chain.
func (a *Actor) GetBlockCount() (uint32, error) {
return a.client.GetBlockCount()
}
// GetNetwork is a convenience method that returns the network's magic number.
func (a *Actor) GetNetwork() netmode.Magic {
return a.version.Protocol.Network
}
// GetVersion returns version data from the RPC endpoint.
func (a *Actor) GetVersion() result.Version {
return *a.version
}
// Send allows to send arbitrary prepared transaction to the network. It returns
// transaction hash and ValidUntilBlock value.
func (a *Actor) Send(tx *transaction.Transaction) (util.Uint256, uint32, error) {
h, err := a.client.SendRawTransaction(tx)
return h, tx.ValidUntilBlock, err
}
// Sign adds signatures to arbitrary transaction using Actor signers wallets.
// Most of the time it shouldn't be used directly since it'll be successful only
// if the transaction is made using the same set of accounts as the one used
// for Actor creation.
func (a *Actor) Sign(tx *transaction.Transaction) error {
if len(tx.Signers) != len(a.signers) {
return errors.New("incorrect number of signers in the transaction")
}
for i, signer := range a.signers {
err := signer.Account.SignTx(a.GetNetwork(), tx)
if err != nil { // then account is non-contract-based and locked, but let's provide more detailed error
if paramNum := len(signer.Account.Contract.Parameters); paramNum != 0 && signer.Account.Contract.Deployed {
return fmt.Errorf("failed to add contract-based witness for signer #%d (%s): "+
"%d parameters must be provided to construct invocation script", i, signer.Account.Address, paramNum)
}
return fmt.Errorf("failed to add witness for signer #%d (%s): account should be unlocked to add the signature. "+
"Store partially-signed transaction and then use 'wallet sign' command to cosign it", i, signer.Account.Address)
}
}
return nil
}
// SignAndSend signs arbitrary transaction (see also Sign) and sends it to the
// network.
func (a *Actor) SignAndSend(tx *transaction.Transaction) (util.Uint256, uint32, error) {
return a.sendWrapper(tx, a.Sign(tx))
}
// sendWrapper simplifies wrapping methods that create transactions.
func (a *Actor) sendWrapper(tx *transaction.Transaction, err error) (util.Uint256, uint32, error) {
if err != nil {
return util.Uint256{}, 0, err
}
return a.Send(tx)
}
// SendCall creates a transaction that calls the given method of the given
// contract with the given parameters (see also MakeCall) and sends it to the
// network.
func (a *Actor) SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) {
return a.sendWrapper(a.MakeCall(contract, method, params...))
}
// SendTunedCall creates a transaction that calls the given method of the given
// contract with the given parameters (see also MakeTunedCall) and attributes,
// allowing to check for execution results of this call and modify transaction
// before it's signed; this transaction is then sent to the network.
func (a *Actor) SendTunedCall(contract util.Uint160, method string, attrs []transaction.Attribute, txHook TransactionCheckerModifier, params ...interface{}) (util.Uint256, uint32, error) {
return a.sendWrapper(a.MakeTunedCall(contract, method, attrs, txHook, params...))
}
// SendRun creates a transaction with the given executable script (see also
// MakeRun) and sends it to the network.
func (a *Actor) SendRun(script []byte) (util.Uint256, uint32, error) {
return a.sendWrapper(a.MakeRun(script))
}
// SendTunedRun creates a transaction with the given executable script and
// attributes, allowing to check for execution results of this script and modify
// transaction before it's signed (see also MakeTunedRun). This transaction is
// then sent to the network.
func (a *Actor) SendTunedRun(script []byte, attrs []transaction.Attribute, txHook TransactionCheckerModifier) (util.Uint256, uint32, error) {
return a.sendWrapper(a.MakeTunedRun(script, attrs, txHook))
}
// SendUncheckedRun creates a transaction with the given executable script and
// attributes that can use up to sysfee GAS for its execution, allowing to modify
// this transaction before it's signed (see also MakeUncheckedRun). This
// transaction is then sent to the network.
func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (util.Uint256, uint32, error) {
return a.sendWrapper(a.MakeUncheckedRun(script, sysfee, attrs, txHook))
}

View file

@ -0,0 +1,254 @@
package actor
import (
"errors"
"testing"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
type RPCClient struct {
err error
invRes *result.Invoke
netFee int64
bCount uint32
version *result.Version
hash util.Uint256
}
func (r *RPCClient) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
return r.invRes, r.err
}
func (r *RPCClient) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
return r.invRes, r.err
}
func (r *RPCClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
return r.invRes, r.err
}
func (r *RPCClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
return r.netFee, r.err
}
func (r *RPCClient) GetBlockCount() (uint32, error) {
return r.bCount, r.err
}
func (r *RPCClient) GetVersion() (*result.Version, error) {
verCopy := *r.version
return &verCopy, r.err
}
func (r *RPCClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
return r.hash, r.err
}
func testRPCAndAccount(t *testing.T) (*RPCClient, *wallet.Account) {
client := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
}
acc, err := wallet.NewAccount()
require.NoError(t, err)
return client, acc
}
func TestNew(t *testing.T) {
client, acc := testRPCAndAccount(t)
// No signers.
_, err := New(client, nil)
require.Error(t, err)
_, err = New(client, []SignerAccount{})
require.Error(t, err)
// Good simple.
a, err := NewSimple(client, acc)
require.NoError(t, err)
require.Equal(t, 1, len(a.signers))
require.Equal(t, 1, len(a.txSigners))
require.Equal(t, transaction.CalledByEntry, a.signers[0].Signer.Scopes)
require.Equal(t, transaction.CalledByEntry, a.txSigners[0].Scopes)
// Contractless account.
badAcc, err := wallet.NewAccount()
require.NoError(t, err)
badAccHash := badAcc.Contract.ScriptHash()
badAcc.Contract = nil
signers := []SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc,
}, {
Signer: transaction.Signer{
Account: badAccHash,
Scopes: transaction.CalledByEntry,
},
Account: badAcc,
}}
_, err = New(client, signers)
require.Error(t, err)
// GetVersion returning error.
client.err = errors.New("bad")
_, err = NewSimple(client, acc)
require.Error(t, err)
client.err = nil
// Account mismatch.
acc2, err := wallet.NewAccount()
require.NoError(t, err)
signers = []SignerAccount{{
Signer: transaction.Signer{
Account: acc2.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc,
}, {
Signer: transaction.Signer{
Account: acc2.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: acc2,
}}
_, err = New(client, signers)
require.Error(t, err)
// Good multiaccount.
signers[0].Signer.Account = acc.Contract.ScriptHash()
a, err = New(client, signers)
require.NoError(t, err)
require.Equal(t, 2, len(a.signers))
require.Equal(t, 2, len(a.txSigners))
}
func TestSimpleWrappers(t *testing.T) {
client, acc := testRPCAndAccount(t)
origVer := *client.version
a, err := NewSimple(client, acc)
require.NoError(t, err)
client.netFee = 42
nf, err := a.CalculateNetworkFee(new(transaction.Transaction))
require.NoError(t, err)
require.Equal(t, int64(42), nf)
client.bCount = 100500
bc, err := a.GetBlockCount()
require.NoError(t, err)
require.Equal(t, uint32(100500), bc)
require.Equal(t, netmode.UnitTestNet, a.GetNetwork())
client.version.Protocol.Network = netmode.TestNet
require.Equal(t, netmode.UnitTestNet, a.GetNetwork())
require.Equal(t, origVer, a.GetVersion())
a, err = NewSimple(client, acc)
require.NoError(t, err)
require.Equal(t, netmode.TestNet, a.GetNetwork())
require.Equal(t, *client.version, a.GetVersion())
client.hash = util.Uint256{1, 2, 3}
h, vub, err := a.Send(&transaction.Transaction{ValidUntilBlock: 123})
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(123), vub)
}
func TestSign(t *testing.T) {
client, acc := testRPCAndAccount(t)
acc2, err := wallet.NewAccount()
require.NoError(t, err)
a, err := New(client, []SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc,
}, {
Signer: transaction.Signer{
Account: acc2.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: &wallet.Account{ // Looks like acc2, but has no private key.
Address: acc2.Address,
EncryptedWIF: acc2.EncryptedWIF,
Contract: acc2.Contract,
},
}})
require.NoError(t, err)
script := []byte{1, 2, 3}
client.hash = util.Uint256{2, 5, 6}
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
tx, err := a.MakeUnsignedRun(script, nil)
require.NoError(t, err)
require.Error(t, a.Sign(tx))
_, _, err = a.SignAndSend(tx)
require.Error(t, err)
}
func TestSenders(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
script := []byte{1, 2, 3}
// Bad.
client.invRes = &result.Invoke{State: "FAULT", GasConsumed: 3, Script: script}
_, _, err = a.SendCall(util.Uint160{1}, "method", 42)
require.Error(t, err)
_, _, err = a.SendTunedCall(util.Uint160{1}, "method", nil, nil, 42)
require.Error(t, err)
_, _, err = a.SendRun(script)
require.Error(t, err)
_, _, err = a.SendTunedRun(script, nil, nil)
require.Error(t, err)
_, _, err = a.SendUncheckedRun(script, 1, nil, func(t *transaction.Transaction) error {
return errors.New("bad")
})
require.Error(t, err)
// Good.
client.hash = util.Uint256{2, 5, 6}
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
h, vub, err := a.SendCall(util.Uint160{1}, "method", 42)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendTunedCall(util.Uint160{1}, "method", nil, nil, 42)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendRun(script)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendTunedRun(script, nil, nil)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendUncheckedRun(script, 1, nil, nil)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
}

View file

@ -0,0 +1,204 @@
package actor
import (
"errors"
"fmt"
"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/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
// TransactionCheckerModifier is a callback that receives the result of
// test-invocation and the transaction that can perform the same invocation
// on chain. This callback is accepted by methods that create transactions, it
// can examine both arguments and return an error if there is anything wrong
// there which will abort the creation process. Notice that when used this
// callback is completely responsible for invocation result checking, including
// checking for HALT execution state (so if you don't check for it in a callback
// you can send a transaction that is known to end up in FAULT state). It can
// also modify the transaction (see TransactionModifier).
type TransactionCheckerModifier func(r *result.Invoke, t *transaction.Transaction) error
// TransactionModifier is a callback that receives the transaction before
// it's signed from a method that creates signed transactions. It can check
// fees and other fields of the transaction and return an error if there is
// anything wrong there which will abort the creation process. It also can modify
// Nonce, SystemFee, NetworkFee and ValidUntilBlock values taking full
// responsibility on the effects of these modifications (smaller fee values, too
// low or too high ValidUntilBlock or bad Nonce can render transaction invalid).
// Modifying other fields is not supported. Mostly it's useful for increasing
// fee values since by default they're just enough for transaction to be
// successfully accepted and executed.
type TransactionModifier func(t *transaction.Transaction) error
// MakeCall creates a transaction that calls the given method of the given
// contract with the given parameters. Test call is performed and checked for
// HALT status, if more checks are needed or transaction should have some
// additional attributes use MakeTunedCall.
func (a *Actor) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
return a.MakeTunedCall(contract, method, nil, nil, params...)
}
// MakeTunedCall creates a transaction with the given attributes that calls the
// given method of the given contract with the given parameters. It's filtered
// through the provided callback (see TransactionCheckerModifier documentation),
// so the process can be aborted and transaction can be modified before signing.
// If no callback is given then the result is checked for HALT state.
func (a *Actor) MakeTunedCall(contract util.Uint160, method string, attrs []transaction.Attribute, txHook TransactionCheckerModifier, params ...interface{}) (*transaction.Transaction, error) {
r, err := a.Call(contract, method, params...)
return a.makeUncheckedWrapper(r, err, attrs, txHook)
}
// MakeRun creates a transaction with the given executable script. Test
// invocation of this script is performed and expected to end up in HALT
// state. If more checks are needed or transaction should have some additional
// attributes use MakeTunedRun.
func (a *Actor) MakeRun(script []byte) (*transaction.Transaction, error) {
return a.MakeTunedRun(script, nil, nil)
}
// MakeTunedRun creates a transaction with the given attributes that executes
// the given script. It's filtered through the provided callback (see
// TransactionCheckerModifier documentation), so the process can be aborted and
// transaction can be modified before signing. If no callback is given then the
// result is checked for HALT state.
func (a *Actor) MakeTunedRun(script []byte, attrs []transaction.Attribute, txHook TransactionCheckerModifier) (*transaction.Transaction, error) {
r, err := a.Run(script)
return a.makeUncheckedWrapper(r, err, attrs, txHook)
}
func (a *Actor) makeUncheckedWrapper(r *result.Invoke, err error, attrs []transaction.Attribute, txHook TransactionCheckerModifier) (*transaction.Transaction, error) {
if err != nil {
return nil, fmt.Errorf("test invocation failed: %w", err)
}
return a.MakeUncheckedRun(r.Script, r.GasConsumed, attrs, func(tx *transaction.Transaction) error {
if txHook == nil {
if r.State != vmstate.Halt.String() {
return fmt.Errorf("script failed (%s state) due to an error: %s", r.State, r.FaultException)
}
return nil
}
return txHook(r, tx)
})
}
// MakeUncheckedRun creates a transaction with the given attributes that executes
// the given script and is expected to use up to sysfee GAS for its execution.
// The transaction is filtered through the provided callback (see
// TransactionModifier documentation), so the process can be aborted and
// transaction can be modified before signing. This method is mostly useful when
// test invocation is already performed and the script and required system fee
// values are already known.
func (a *Actor) MakeUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (*transaction.Transaction, error) {
tx, err := a.MakeUnsignedUncheckedRun(script, sysfee, attrs)
if err != nil {
return nil, err
}
if txHook != nil {
err = txHook(tx)
if err != nil {
return nil, err
}
}
err = a.Sign(tx)
if err != nil {
return nil, err
}
return tx, nil
}
// MakeUnsignedCall creates an unsigned transaction with the given attributes
// that calls the given method of the given contract with the given parameters.
// Test-invocation is performed and is expected to end up in HALT state, the
// transaction returned has correct SystemFee and NetworkFee values.
func (a *Actor) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
r, err := a.Call(contract, method, params...)
return a.makeUnsignedWrapper(r, err, attrs)
}
// MakeUnsignedRun creates an unsigned transaction with the given attributes
// that executes the given script. Test-invocation is performed and is expected
// to end up in HALT state, the transaction returned has correct SystemFee and
// NetworkFee values.
func (a *Actor) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
r, err := a.Run(script)
return a.makeUnsignedWrapper(r, err, attrs)
}
func (a *Actor) makeUnsignedWrapper(r *result.Invoke, err error, attrs []transaction.Attribute) (*transaction.Transaction, error) {
if err != nil {
return nil, fmt.Errorf("failed to test-invoke: %w", err)
}
if r.State != vmstate.Halt.String() {
return nil, fmt.Errorf("test invocation faulted (%s): %s", r.State, r.FaultException)
}
return a.MakeUnsignedUncheckedRun(r.Script, r.GasConsumed, attrs)
}
// MakeUnsignedUncheckedRun creates an unsigned transaction containing the given
// script with the system fee value and attributes. It's expected to be used when
// test invocation is already done and the script and system fee value are already
// known to be good, so it doesn't do test invocation internally. But it fills
// Signers with Actor's signers, calculates proper ValidUntilBlock and NetworkFee
// values. The resulting transaction can be changed in its Nonce, SystemFee,
// NetworkFee and ValidUntilBlock values and then be signed and sent or
// exchanged via context.ParameterContext.
func (a *Actor) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []transaction.Attribute) (*transaction.Transaction, error) {
var err error
if len(script) == 0 {
return nil, errors.New("empty script")
}
if sysFee < 0 {
return nil, errors.New("negative system fee")
}
tx := transaction.New(script, sysFee)
tx.Signers = a.txSigners
tx.Attributes = attrs
tx.ValidUntilBlock, err = a.CalculateValidUntilBlock()
if err != nil {
return nil, fmt.Errorf("calculating validUntilBlock: %w", err)
}
tx.Scripts = make([]transaction.Witness, len(a.signers))
for i := range a.signers {
if !a.signers[i].Account.Contract.Deployed {
tx.Scripts[i].VerificationScript = a.signers[i].Account.Contract.Script
}
}
// CalculateNetworkFee doesn't call Hash or Size, only serializes the
// transaction via Bytes, so it's safe wrt internal caching.
tx.NetworkFee, err = a.client.CalculateNetworkFee(tx)
if err != nil {
return nil, fmt.Errorf("calculating network fee: %w", err)
}
return tx, nil
}
// CalculateValidUntilBlock returns correct ValidUntilBlock value for a new
// transaction relative to the current blockchain height. It uses "height +
// number of validators + 1" formula suggesting shorter transaction lifetime
// than the usual "height + MaxValidUntilBlockIncrement" approach. Shorter
// lifetime can be useful to control transaction acceptance wait time because
// it can't be added into a block after ValidUntilBlock.
func (a *Actor) CalculateValidUntilBlock() (uint32, error) {
blockCount, err := a.client.GetBlockCount()
if err != nil {
return 0, fmt.Errorf("can't get block count: %w", err)
}
var vc = int(a.version.Protocol.ValidatorsCount)
var bestH = uint32(0)
for h, n := range a.version.Protocol.ValidatorsHistory { // In case it's enabled.
if h >= bestH && h <= blockCount {
vc = n
bestH = h
}
}
return blockCount + uint32(vc+1), nil
}

View file

@ -0,0 +1,178 @@
package actor
import (
"errors"
"testing"
"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/util"
"github.com/stretchr/testify/require"
)
func TestCalculateValidUntilBlock(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
client.err = errors.New("error")
_, err = a.CalculateValidUntilBlock()
require.Error(t, err)
client.err = nil
client.bCount = 42
vub, err := a.CalculateValidUntilBlock()
require.NoError(t, err)
require.Equal(t, uint32(42+7+1), vub)
client.version.Protocol.ValidatorsHistory = map[uint32]int{
0: 7,
40: 4,
80: 10,
}
a, err = NewSimple(client, acc)
require.NoError(t, err)
vub, err = a.CalculateValidUntilBlock()
require.NoError(t, err)
require.Equal(t, uint32(42+4+1), vub)
client.bCount = 101
vub, err = a.CalculateValidUntilBlock()
require.NoError(t, err)
require.Equal(t, uint32(101+10+1), vub)
}
func TestMakeUnsigned(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
// Bad parameters.
script := []byte{1, 2, 3}
_, err = a.MakeUnsignedUncheckedRun(script, -1, nil)
require.Error(t, err)
_, err = a.MakeUnsignedUncheckedRun([]byte{}, 1, nil)
require.Error(t, err)
_, err = a.MakeUnsignedUncheckedRun(nil, 1, nil)
require.Error(t, err)
// RPC error.
client.err = errors.New("err")
_, err = a.MakeUnsignedUncheckedRun(script, 1, nil)
require.Error(t, err)
// Good unchecked.
client.netFee = 42
client.bCount = 100500
client.err = nil
tx, err := a.MakeUnsignedUncheckedRun(script, 1, nil)
require.NoError(t, err)
require.Equal(t, script, tx.Script)
require.Equal(t, 1, len(tx.Signers))
require.Equal(t, acc.Contract.ScriptHash(), tx.Signers[0].Account)
require.Equal(t, 1, len(tx.Scripts))
require.Equal(t, acc.Contract.Script, tx.Scripts[0].VerificationScript)
require.Nil(t, tx.Scripts[0].InvocationScript)
// Bad run.
client.err = errors.New("")
_, err = a.MakeUnsignedRun(script, nil)
require.Error(t, err)
// Faulted run.
client.invRes = &result.Invoke{State: "FAULT", GasConsumed: 3, Script: script}
client.err = nil
_, err = a.MakeUnsignedRun(script, nil)
require.Error(t, err)
// Good run.
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
_, err = a.MakeUnsignedRun(script, nil)
require.NoError(t, err)
}
func TestMakeSigned(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
// Bad script.
_, err = a.MakeUncheckedRun(nil, 0, nil, nil)
require.Error(t, err)
// Good, no hook.
script := []byte{1, 2, 3}
_, err = a.MakeUncheckedRun(script, 0, nil, nil)
require.NoError(t, err)
// Bad, can't sign because of a hook.
_, err = a.MakeUncheckedRun(script, 0, nil, func(t *transaction.Transaction) error {
t.Signers = append(t.Signers, transaction.Signer{})
return nil
})
require.Error(t, err)
// Bad, hook returns an error.
_, err = a.MakeUncheckedRun(script, 0, nil, func(t *transaction.Transaction) error {
return errors.New("")
})
require.Error(t, err)
// Good with a hook.
tx, err := a.MakeUncheckedRun(script, 0, nil, func(t *transaction.Transaction) error {
t.ValidUntilBlock = 777
return nil
})
require.NoError(t, err)
require.Equal(t, uint32(777), tx.ValidUntilBlock)
// Checked
// Bad, invocation fails.
client.err = errors.New("")
_, err = a.MakeTunedRun(script, nil, func(r *result.Invoke, t *transaction.Transaction) error {
return nil
})
require.Error(t, err)
// Bad, hook returns an error.
client.err = nil
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
_, err = a.MakeTunedRun(script, nil, func(r *result.Invoke, t *transaction.Transaction) error {
return errors.New("")
})
require.Error(t, err)
// Good, no hook.
_, err = a.MakeTunedRun(script, []transaction.Attribute{{Type: transaction.HighPriority}}, nil)
require.NoError(t, err)
_, err = a.MakeRun(script)
require.NoError(t, err)
// Bad, invocation returns FAULT.
client.invRes = &result.Invoke{State: "FAULT", GasConsumed: 3, Script: script}
_, err = a.MakeTunedRun(script, nil, nil)
require.Error(t, err)
// Good, invocation returns FAULT, but callback ignores it.
_, err = a.MakeTunedRun(script, nil, func(r *result.Invoke, t *transaction.Transaction) error {
return nil
})
require.NoError(t, err)
// Good, via call and with a callback.
_, err = a.MakeTunedCall(util.Uint160{}, "something", []transaction.Attribute{{Type: transaction.HighPriority}}, func(r *result.Invoke, t *transaction.Transaction) error {
return nil
}, "param", 1)
require.NoError(t, err)
// Bad, it still is a FAULT.
_, err = a.MakeCall(util.Uint160{}, "method")
require.Error(t, err)
// Good.
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
_, err = a.MakeCall(util.Uint160{}, "method", 1)
require.NoError(t, err)
}

View file

@ -97,6 +97,9 @@ func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64,
// CreateTxFromScript creates transaction and properly sets cosigners and NetworkFee. // CreateTxFromScript creates transaction and properly sets cosigners and NetworkFee.
// If sysFee <= 0, it is determined via result of `invokescript` RPC. You should // If sysFee <= 0, it is determined via result of `invokescript` RPC. You should
// initialize network magic with Init before calling CreateTxFromScript. // initialize network magic with Init before calling CreateTxFromScript.
//
// Deprecated: please use actor.Actor API, this method will be removed in future
// versions.
func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, netFee int64, func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, netFee int64,
cosigners []SignerAccount) (*transaction.Transaction, error) { cosigners []SignerAccount) (*transaction.Transaction, error) {
signers, accounts, err := getSigners(acc, cosigners) signers, accounts, err := getSigners(acc, cosigners)

View file

@ -755,6 +755,9 @@ func (c *Client) SubmitRawOracleResponse(ps []interface{}) error {
// possible. It spends the amount of gas specified. It returns a hash of the // possible. It spends the amount of gas specified. It returns a hash of the
// invocation transaction and an error. If one of the cosigners accounts is // invocation transaction and an error. If one of the cosigners accounts is
// neither contract-based nor unlocked, an error is returned. // neither contract-based nor unlocked, an error is returned.
//
// Deprecated: please use actor.Actor API, this method will be removed in future
// versions.
func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sysfee int64, netfee fixedn.Fixed8, cosigners []SignerAccount) (util.Uint256, error) { func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sysfee int64, netfee fixedn.Fixed8, cosigners []SignerAccount) (util.Uint256, error) {
tx, err := c.CreateTxFromScript(script, acc, sysfee, int64(netfee), cosigners) tx, err := c.CreateTxFromScript(script, acc, sysfee, int64(netfee), cosigners)
if err != nil { if err != nil {
@ -767,6 +770,9 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
// it to the chain. It returns a hash of the transaction and an error. If one of // it to the chain. It returns a hash of the transaction and an error. If one of
// the cosigners accounts is neither contract-based nor unlocked, an error is // the cosigners accounts is neither contract-based nor unlocked, an error is
// returned. // returned.
//
// Deprecated: please use actor.Actor API, this method will be removed in future
// versions.
func (c *Client) SignAndPushTx(tx *transaction.Transaction, acc *wallet.Account, cosigners []SignerAccount) (util.Uint256, error) { func (c *Client) SignAndPushTx(tx *transaction.Transaction, acc *wallet.Account, cosigners []SignerAccount) (util.Uint256, error) {
var ( var (
txHash util.Uint256 txHash util.Uint256
@ -1007,6 +1013,9 @@ func (c *Client) ValidateAddress(address string) error {
// current blockchain height + number of validators. Number of validators // current blockchain height + number of validators. Number of validators
// is the length of blockchain validators list got from GetNextBlockValidators() // is the length of blockchain validators list got from GetNextBlockValidators()
// method. Validators count is being cached and updated every 100 blocks. // method. Validators count is being cached and updated every 100 blocks.
//
// Deprecated: please use (*Actor).CalculateValidUntilBlock. This method will be
// removed in future versions.
func (c *Client) CalculateValidUntilBlock() (uint32, error) { func (c *Client) CalculateValidUntilBlock() (uint32, error) {
var ( var (
result uint32 result uint32
@ -1040,6 +1049,9 @@ func (c *Client) CalculateValidUntilBlock() (uint32, error) {
// AddNetworkFee adds network fee for each witness script and optional extra // AddNetworkFee adds network fee for each witness script and optional extra
// network fee to transaction. `accs` is an array signer's accounts. // network fee to transaction. `accs` is an array signer's accounts.
//
// Deprecated: please use CalculateNetworkFee or actor.Actor. This method will
// be removed in future versions.
func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error { func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
if len(tx.Signers) != len(accs) { if len(tx.Signers) != len(accs) {
return errors.New("number of signers must match number of scripts") return errors.New("number of signers must match number of scripts")

View file

@ -116,7 +116,7 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
Account: accs[0].PrivateKey().GetScriptHash(), Account: accs[0].PrivateKey().GetScriptHash(),
Scopes: transaction.CalledByEntry, Scopes: transaction.CalledByEntry,
}} }}
require.Error(t, c.AddNetworkFee(tx, extraFee, accs[0], accs[1])) require.Error(t, c.AddNetworkFee(tx, extraFee, accs[0], accs[1])) //nolint:staticcheck // SA1019: c.AddNetworkFee is deprecated
}) })
t.Run("Simple", func(t *testing.T) { t.Run("Simple", func(t *testing.T) {
acc0 := wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)) acc0 := wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0))
@ -137,7 +137,7 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
tx.Scripts = nil tx.Scripts = nil
require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0)) require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0)) //nolint:staticcheck // SA1019: c.AddNetworkFee is deprecated
actual := tx.NetworkFee actual := tx.NetworkFee
require.NoError(t, acc0.SignTx(testchain.Network(), tx)) require.NoError(t, acc0.SignTx(testchain.Network(), tx))
@ -203,7 +203,7 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
tx.Scripts = nil tx.Scripts = nil
require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0, acc1)) require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0, acc1)) //nolint:staticcheck // SA1019: c.AddNetworkFee is deprecated
actual := tx.NetworkFee actual := tx.NetworkFee
require.NoError(t, acc0.SignTx(testchain.Network(), tx)) require.NoError(t, acc0.SignTx(testchain.Network(), tx))
@ -276,7 +276,7 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
tx.Scripts = nil tx.Scripts = nil
require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0, acc1)) require.NoError(t, c.AddNetworkFee(tx, extraFee, acc0, acc1)) //nolint:staticcheck // SA1019: c.AddNetworkFee is deprecated
require.NoError(t, acc0.SignTx(testchain.Network(), tx)) require.NoError(t, acc0.SignTx(testchain.Network(), tx))
tx.Scripts = append(tx.Scripts, transaction.Witness{}) tx.Scripts = append(tx.Scripts, transaction.Witness{})
require.Equal(t, tx.NetworkFee, actual+extraFee) require.Equal(t, tx.NetworkFee, actual+extraFee)
@ -315,7 +315,7 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
Scopes: transaction.Global, Scopes: transaction.Global,
}, },
} }
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) //nolint:staticcheck // SA1019: c.AddNetworkFee is deprecated
}) })
t.Run("InvalidContract", func(t *testing.T) { t.Run("InvalidContract", func(t *testing.T) {
tx := newTx(t) tx := newTx(t)
@ -330,7 +330,7 @@ func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
Scopes: transaction.Global, Scopes: transaction.Global,
}, },
} }
require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) require.Error(t, c.AddNetworkFee(tx, 10, acc0, acc1)) //nolint:staticcheck // SA1019: c.AddNetworkFee is deprecated
}) })
}) })
} }
@ -458,7 +458,7 @@ func TestSignAndPushInvocationTx(t *testing.T) {
t.Run("good", func(t *testing.T) { t.Run("good", func(t *testing.T) {
t.Run("signer0: sig", func(t *testing.T) { t.Run("signer0: sig", func(t *testing.T) {
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
{ {
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: priv0.GetScriptHash(), Account: priv0.GetScriptHash(),
@ -471,7 +471,7 @@ func TestSignAndPushInvocationTx(t *testing.T) {
check(t, h) check(t, h)
}) })
t.Run("signer0: sig; signer1: sig", func(t *testing.T) { t.Run("signer0: sig; signer1: sig", func(t *testing.T) {
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
{ {
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: priv0.GetScriptHash(), Account: priv0.GetScriptHash(),
@ -491,7 +491,7 @@ func TestSignAndPushInvocationTx(t *testing.T) {
check(t, h) check(t, h)
}) })
t.Run("signer0: sig; signer1: contract-based paramless", func(t *testing.T) { t.Run("signer0: sig; signer1: contract-based paramless", func(t *testing.T) {
h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ h, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
{ {
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: priv0.GetScriptHash(), Account: priv0.GetScriptHash(),
@ -513,7 +513,7 @@ func TestSignAndPushInvocationTx(t *testing.T) {
}) })
t.Run("error", func(t *testing.T) { t.Run("error", func(t *testing.T) {
t.Run("signer0: sig; signer1: contract-based with params", func(t *testing.T) { t.Run("signer0: sig; signer1: contract-based with params", func(t *testing.T) {
_, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ _, err := c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
{ {
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: priv0.GetScriptHash(), Account: priv0.GetScriptHash(),
@ -541,7 +541,7 @@ func TestSignAndPushInvocationTx(t *testing.T) {
Parameters: []wallet.ContractParam{{Name: "parameter0", Type: smartcontract.SignatureType}}, Parameters: []wallet.ContractParam{{Name: "parameter0", Type: smartcontract.SignatureType}},
}, },
} }
_, err = c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ _, err = c.SignAndPushInvocationTx([]byte{byte(opcode.PUSH1)}, acc0, 30, 0, []rpcclient.SignerAccount{ //nolint:staticcheck // SA1019: c.SignAndPushInvocationTx is deprecated
{ {
Signer: transaction.Signer{ Signer: transaction.Signer{
Account: priv0.GetScriptHash(), Account: priv0.GetScriptHash(),
@ -693,7 +693,7 @@ func TestCreateTxFromScript(t *testing.T) {
priv := testchain.PrivateKey(0) priv := testchain.PrivateKey(0)
acc := wallet.NewAccountFromPrivateKey(priv) acc := wallet.NewAccountFromPrivateKey(priv)
t.Run("NoSystemFee", func(t *testing.T) { t.Run("NoSystemFee", func(t *testing.T) {
tx, err := c.CreateTxFromScript([]byte{byte(opcode.PUSH1)}, acc, -1, 10, nil) tx, err := c.CreateTxFromScript([]byte{byte(opcode.PUSH1)}, acc, -1, 10, nil) //nolint:staticcheck // SA1019: c.CreateTxFromScript is deprecated
require.NoError(t, err) require.NoError(t, err)
require.True(t, tx.ValidUntilBlock > chain.BlockHeight()) require.True(t, tx.ValidUntilBlock > chain.BlockHeight())
require.EqualValues(t, 30, tx.SystemFee) // PUSH1 require.EqualValues(t, 30, tx.SystemFee) // PUSH1
@ -701,7 +701,7 @@ func TestCreateTxFromScript(t *testing.T) {
require.Equal(t, acc.PrivateKey().GetScriptHash(), tx.Signers[0].Account) require.Equal(t, acc.PrivateKey().GetScriptHash(), tx.Signers[0].Account)
}) })
t.Run("ProvideSystemFee", func(t *testing.T) { t.Run("ProvideSystemFee", func(t *testing.T) {
tx, err := c.CreateTxFromScript([]byte{byte(opcode.PUSH1)}, acc, 123, 10, nil) tx, err := c.CreateTxFromScript([]byte{byte(opcode.PUSH1)}, acc, 123, 10, nil) //nolint:staticcheck // SA1019: c.CreateTxFromScript is deprecated
require.NoError(t, err) require.NoError(t, err)
require.True(t, tx.ValidUntilBlock > chain.BlockHeight()) require.True(t, tx.ValidUntilBlock > chain.BlockHeight())
require.EqualValues(t, 123, tx.SystemFee) require.EqualValues(t, 123, tx.SystemFee)

View file

@ -694,7 +694,11 @@ func (s *Server) getVersion(_ params.Params) (interface{}, *neorpc.Error) {
MemoryPoolMaxTransactions: cfg.MemPoolSize, MemoryPoolMaxTransactions: cfg.MemPoolSize,
ValidatorsCount: byte(cfg.GetNumOfCNs(s.chain.BlockHeight())), ValidatorsCount: byte(cfg.GetNumOfCNs(s.chain.BlockHeight())),
InitialGasDistribution: cfg.InitialGASSupply, InitialGasDistribution: cfg.InitialGASSupply,
StateRootInHeader: cfg.StateRootInHeader,
CommitteeHistory: cfg.CommitteeHistory,
P2PSigExtensions: cfg.P2PSigExtensions,
StateRootInHeader: cfg.StateRootInHeader,
ValidatorsHistory: cfg.ValidatorsHistory,
}, },
}, nil }, nil
} }

View file

@ -875,7 +875,11 @@ var rpcTestCases = map[string][]rpcTestCase{
require.EqualValues(t, cfg.MemPoolSize, resp.Protocol.MemoryPoolMaxTransactions) require.EqualValues(t, cfg.MemPoolSize, resp.Protocol.MemoryPoolMaxTransactions)
require.EqualValues(t, cfg.ValidatorsCount, resp.Protocol.ValidatorsCount) require.EqualValues(t, cfg.ValidatorsCount, resp.Protocol.ValidatorsCount)
require.EqualValues(t, cfg.InitialGASSupply, resp.Protocol.InitialGasDistribution) require.EqualValues(t, cfg.InitialGASSupply, resp.Protocol.InitialGasDistribution)
require.EqualValues(t, false, resp.Protocol.StateRootInHeader)
require.Equal(t, 0, len(resp.Protocol.CommitteeHistory))
require.True(t, resp.Protocol.P2PSigExtensions) // Yeah, notary is enabled.
require.False(t, resp.Protocol.StateRootInHeader)
require.Equal(t, 0, len(resp.Protocol.ValidatorsHistory))
}, },
}, },
}, },

View file

@ -5,8 +5,8 @@ import (
"encoding/hex" "encoding/hex"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
@ -34,8 +34,8 @@ func ExampleBuilder() {
w, _ := wallet.NewWalletFromFile("somewhere") w, _ := wallet.NewWalletFromFile("somewhere")
// Assuming there is one Account inside // Assuming there is one Account inside
acc := w.Accounts[0] a, _ := actor.NewSimple(c, w.Accounts[0])
from, _ := address.StringToUint160(acc.Address) from := w.Accounts[0].Contract.ScriptHash() // Assuming Contract is present.
// Multiple transfers in a single script. If any of them fail whole script fails. // Multiple transfers in a single script. If any of them fail whole script fails.
b.InvokeWithAssert(neoHash, "transfer", from, util.Uint160{0x70}, 1, nil) b.InvokeWithAssert(neoHash, "transfer", from, util.Uint160{0x70}, 1, nil)
@ -44,6 +44,7 @@ func ExampleBuilder() {
script, _ = b.Script() script, _ = b.Script()
// The script can then be used to create transaction or to invoke via RPC. // The script can then be used to create transaction or to invoke via RPC.
txid, _ := c.SignAndPushInvocationTx(script, acc, -1, 0, nil) txid, vub, _ := a.SendRun(script)
_ = txid _ = txid
_ = vub
} }

View file

@ -303,6 +303,10 @@ func NewParameterFromValue(value interface{}) (Parameter, error) {
case uint64: case uint64:
result.Type = IntegerType result.Type = IntegerType
result.Value = new(big.Int).SetUint64(v) result.Value = new(big.Int).SetUint64(v)
case *Parameter:
result = *v
case Parameter:
result = v
case util.Uint160: case util.Uint160:
result.Type = Hash160Type result.Type = Hash160Type
case util.Uint256: case util.Uint256:
@ -321,6 +325,11 @@ func NewParameterFromValue(value interface{}) (Parameter, error) {
} }
result.Type = ArrayType result.Type = ArrayType
result.Value = arr result.Value = arr
case []Parameter:
arr := make([]Parameter, len(v))
copy(arr, v)
result.Type = ArrayType
result.Value = arr
case []*keys.PublicKey: case []*keys.PublicKey:
return NewParameterFromValue(keys.PublicKeys(v)) return NewParameterFromValue(keys.PublicKeys(v))
case keys.PublicKeys: case keys.PublicKeys:

View file

@ -616,6 +616,16 @@ func TestParameterFromValue(t *testing.T) {
expType: IntegerType, expType: IntegerType,
expVal: big.NewInt(100), expVal: big.NewInt(100),
}, },
{
value: Parameter{ByteArrayType, []byte{1, 2, 3}},
expType: ByteArrayType,
expVal: []byte{1, 2, 3},
},
{
value: &Parameter{ByteArrayType, []byte{1, 2, 3}},
expType: ByteArrayType,
expVal: []byte{1, 2, 3},
},
{ {
value: util.Uint160{1, 2, 3}, value: util.Uint160{1, 2, 3},
expType: Hash160Type, expType: Hash160Type,
@ -641,6 +651,11 @@ func TestParameterFromValue(t *testing.T) {
expType: ArrayType, expType: ArrayType,
expVal: []Parameter{{ByteArrayType, []byte{1, 2, 3}}, {ByteArrayType, []byte{3, 2, 1}}}, expVal: []Parameter{{ByteArrayType, []byte{1, 2, 3}}, {ByteArrayType, []byte{3, 2, 1}}},
}, },
{
value: []Parameter{{ByteArrayType, []byte{1, 2, 3}}, {ByteArrayType, []byte{3, 2, 1}}},
expType: ArrayType,
expVal: []Parameter{{ByteArrayType, []byte{1, 2, 3}}, {ByteArrayType, []byte{3, 2, 1}}},
},
{ {
value: []*keys.PublicKey{pk1.PublicKey(), pk2.PublicKey()}, value: []*keys.PublicKey{pk1.PublicKey(), pk2.PublicKey()},
expType: ArrayType, expType: ArrayType,

View file

@ -85,7 +85,9 @@ func NewAccount() (*Account, error) {
// SignTx signs transaction t and updates it's Witnesses. // SignTx signs transaction t and updates it's Witnesses.
func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error { func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error {
if len(a.Contract.Parameters) == 0 { if len(a.Contract.Parameters) == 0 {
t.Scripts = append(t.Scripts, transaction.Witness{}) if len(t.Signers) != len(t.Scripts) { // Sequential signing vs. existing scripts.
t.Scripts = append(t.Scripts, transaction.Witness{})
}
return nil return nil
} }
if a.privateKey == nil { if a.privateKey == nil {