forked from TrueCloudLab/neoneo-go
Merge pull request #2632 from nspcc-dev/rpcclient-actor
RPC client Actor interface
This commit is contained in:
commit
593fa4cac8
20 changed files with 1037 additions and 77 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
204
pkg/rpcclient/actor/actor.go
Normal file
204
pkg/rpcclient/actor/actor.go
Normal 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))
|
||||||
|
}
|
254
pkg/rpcclient/actor/actor_test.go
Normal file
254
pkg/rpcclient/actor/actor_test.go
Normal 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)
|
||||||
|
}
|
204
pkg/rpcclient/actor/maker.go
Normal file
204
pkg/rpcclient/actor/maker.go
Normal 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
|
||||||
|
}
|
178
pkg/rpcclient/actor/maker_test.go
Normal file
178
pkg/rpcclient/actor/maker_test.go
Normal 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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue