cli: add await flag for operations with transactions
New --await flag is an option to synchronize on transaction execution for CLI commands. Closes #3244 Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
This commit is contained in:
parent
4f5e3f363a
commit
0ffa24932b
18 changed files with 383 additions and 39 deletions
|
@ -328,6 +328,14 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID1)
|
||||
|
||||
// check --await flag
|
||||
tokenID2 := mint(t)
|
||||
e.In.WriteString(nftOwnerPass + "\r")
|
||||
e.Run(t, append(cmdTransfer, "--await", "--id", hex.EncodeToString(tokenID2))...)
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||
checkBalanceResult(t, nftOwnerAddr, tokenID1)
|
||||
|
||||
// transfer: good, to NEP-11-Payable contract, with data
|
||||
verifyH := deployVerifyContract(t, e)
|
||||
cmdTransfer = []string{
|
||||
|
|
|
@ -223,12 +223,19 @@ func TestNEP17Transfer(t *testing.T) {
|
|||
"neo-go", "wallet", "nep17", "transfer",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--to", address.Uint160ToString(e.Chain.GetNotaryContractScriptHash()),
|
||||
"--token", "GAS",
|
||||
"--amount", "1",
|
||||
"--from", testcli.ValidatorAddr,
|
||||
"--force",
|
||||
"[", testcli.ValidatorAddr, strconv.Itoa(int(validTil)), "]"}
|
||||
"--from", testcli.ValidatorAddr}
|
||||
|
||||
t.Run("with await", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, append(cmd, "--to", nftOwnerAddr, "--await")...)
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
})
|
||||
|
||||
cmd = append(cmd, "--to", address.Uint160ToString(e.Chain.GetNotaryContractScriptHash()),
|
||||
"[", testcli.ValidatorAddr, strconv.Itoa(int(validTil)), "]")
|
||||
|
||||
t.Run("with data", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
|
|
|
@ -32,8 +32,14 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// DefaultTimeout is the default timeout used for RPC requests.
|
||||
const DefaultTimeout = 10 * time.Second
|
||||
const (
|
||||
// DefaultTimeout is the default timeout used for RPC requests.
|
||||
DefaultTimeout = 10 * time.Second
|
||||
// DefaultAwaitableTimeout is the default timeout used for RPC requests that
|
||||
// require transaction awaiting. It is set to the approximate time of three
|
||||
// Neo N3 mainnet blocks accepting.
|
||||
DefaultAwaitableTimeout = 3 * 15 * time.Second
|
||||
)
|
||||
|
||||
// RPCEndpointFlag is a long flag name for an RPC endpoint. It can be used to
|
||||
// check for flag presence in the context.
|
||||
|
@ -129,6 +135,9 @@ func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) {
|
|||
if dur == 0 {
|
||||
dur = DefaultTimeout
|
||||
}
|
||||
if !ctx.IsSet("timeout") && ctx.Bool("await") {
|
||||
dur = DefaultAwaitableTimeout
|
||||
}
|
||||
return context.WithTimeout(context.Background(), dur)
|
||||
}
|
||||
|
||||
|
|
|
@ -338,7 +338,7 @@ func TestContractDeployWithData(t *testing.T) {
|
|||
"--config", "testdata/deploy/neo-go.yml",
|
||||
"--out", nefName, "--manifest", manifestName)
|
||||
|
||||
deployContract := func(t *testing.T, haveData bool, scope string) {
|
||||
deployContract := func(t *testing.T, haveData bool, scope string, await bool) {
|
||||
e := testcli.NewExecutor(t, true)
|
||||
cmd := []string{
|
||||
"neo-go", "contract", "deploy",
|
||||
|
@ -348,6 +348,9 @@ func TestContractDeployWithData(t *testing.T) {
|
|||
"--force",
|
||||
}
|
||||
|
||||
if await {
|
||||
cmd = append(cmd, "--await")
|
||||
}
|
||||
if haveData {
|
||||
cmd = append(cmd, "[", "key1", "12", "key2", "take_me_to_church", "]")
|
||||
}
|
||||
|
@ -358,8 +361,13 @@ func TestContractDeployWithData(t *testing.T) {
|
|||
}
|
||||
e.In.WriteString(testcli.ValidatorPass + "\r")
|
||||
e.Run(t, cmd...)
|
||||
var tx *transaction.Transaction
|
||||
if await {
|
||||
tx, _ = e.CheckAwaitableTxPersisted(t)
|
||||
} else {
|
||||
tx, _ = e.CheckTxPersisted(t)
|
||||
}
|
||||
|
||||
tx, _ := e.CheckTxPersisted(t, "Sent invocation transaction ")
|
||||
require.Equal(t, scope, tx.Signers[0].Scopes.String())
|
||||
if !haveData {
|
||||
return
|
||||
|
@ -396,9 +404,12 @@ func TestContractDeployWithData(t *testing.T) {
|
|||
require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value())
|
||||
}
|
||||
|
||||
deployContract(t, true, "")
|
||||
deployContract(t, false, "Global")
|
||||
deployContract(t, true, "Global")
|
||||
deployContract(t, true, "", false)
|
||||
deployContract(t, false, "Global", false)
|
||||
deployContract(t, true, "Global", false)
|
||||
deployContract(t, false, "", true)
|
||||
deployContract(t, true, "Global", true)
|
||||
deployContract(t, true, "", true)
|
||||
}
|
||||
|
||||
func TestDeployWithSigners(t *testing.T) {
|
||||
|
@ -772,6 +783,12 @@ func TestComlileAndInvokeFunction(t *testing.T) {
|
|||
e.Run(t, append(cmd, h.StringLE(), "getValue",
|
||||
"--", testcli.ValidatorAddr, hVerify.StringLE())...)
|
||||
})
|
||||
|
||||
t.Run("with await", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, append(cmd, "--force", "--await", h.StringLE(), "getValue")...)
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("real invoke and save tx", func(t *testing.T) {
|
||||
|
|
|
@ -91,6 +91,7 @@ func NewCommands() []cli.Command {
|
|||
txctx.SysGasFlag,
|
||||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
}
|
||||
invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...)
|
||||
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
|
||||
|
@ -188,10 +189,12 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "deploy",
|
||||
Usage: "deploy a smart contract (.nef with description)",
|
||||
UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [data]",
|
||||
UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [data]",
|
||||
Description: `Deploys given contract into the chain. The gas parameter is for additional
|
||||
gas to be added as a network fee to prioritize the transaction. The data
|
||||
parameter is an optional parameter to be passed to '_deploy' method.
|
||||
parameter is an optional parameter to be passed to '_deploy' method. When
|
||||
--await flag is specified, it waits for the transaction to be included
|
||||
in a block.
|
||||
`,
|
||||
Action: contractDeploy,
|
||||
Flags: deployFlags,
|
||||
|
@ -201,13 +204,14 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "invokefunction",
|
||||
Usage: "invoke deployed contract on the blockchain",
|
||||
UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] scripthash [method] [arguments...] [--] [signers...]",
|
||||
UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [method] [arguments...] [--] [signers...]",
|
||||
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||
arguments and signers. Sender is included in the list of signers by default
|
||||
with None witness scope. If you'd like to change default sender's scope,
|
||||
specify it via signers parameter. See testinvokefunction documentation for
|
||||
the details about parameters. It differs from testinvokefunction in that this
|
||||
command sends an invocation transaction to the network.
|
||||
command sends an invocation transaction to the network. When --await flag is
|
||||
specified, it waits for the transaction to be included in a block.
|
||||
`,
|
||||
Action: invokeFunction,
|
||||
Flags: invokeFunctionFlags,
|
||||
|
|
|
@ -5,13 +5,16 @@ package txctx
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
@ -37,6 +40,11 @@ var (
|
|||
Name: "force",
|
||||
Usage: "Do not ask for a confirmation (and ignore errors)",
|
||||
}
|
||||
// AwaitFlag is a flag used to wait for the transaction to be included in a block.
|
||||
AwaitFlag = cli.BoolFlag{
|
||||
Name: "await",
|
||||
Usage: "wait for the transaction to be included in a block",
|
||||
}
|
||||
)
|
||||
|
||||
// SignAndSend adds network and system fees to the provided transaction and
|
||||
|
@ -48,6 +56,7 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr
|
|||
gas = flags.Fixed8FromContext(ctx, "gas")
|
||||
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
|
||||
ver = act.GetVersion()
|
||||
aer *state.AppExecResult
|
||||
)
|
||||
|
||||
tx.SystemFee += int64(sysgas)
|
||||
|
@ -68,12 +77,37 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr
|
|||
// Compensate for confirmation waiting.
|
||||
tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1
|
||||
}
|
||||
_, _, err = act.SignAndSend(tx)
|
||||
var (
|
||||
resTx util.Uint256
|
||||
vub uint32
|
||||
)
|
||||
resTx, vub, err = act.SignAndSend(tx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if ctx.Bool("await") {
|
||||
aer, err = act.Wait(resTx, vub, err)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
||||
DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DumpTransactionInfo prints transaction info to the given writer.
|
||||
func DumpTransactionInfo(w io.Writer, h util.Uint256, res *state.AppExecResult) {
|
||||
fmt.Fprintln(w, h.StringLE())
|
||||
if res != nil {
|
||||
fmt.Fprintf(w, "OnChain:\t%t\n", res != nil)
|
||||
fmt.Fprintf(w, "VMState:\t%s\n", res.VMState.String())
|
||||
if res.FaultException != "" {
|
||||
fmt.Fprintf(w, "FaultException:\t%s\n", res.FaultException)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/txctx"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"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/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/urfave/cli"
|
||||
|
@ -55,7 +59,7 @@ func cancelTx(ctx *cli.Context) error {
|
|||
return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1)
|
||||
}
|
||||
|
||||
resHash, _, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error {
|
||||
resHash, resVub, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error {
|
||||
err := actor.DefaultCheckerModifier(r, t)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -72,6 +76,29 @@ func cancelTx(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to send conflicting transaction: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, resHash.StringLE())
|
||||
var (
|
||||
acceptedH = resHash
|
||||
res *state.AppExecResult
|
||||
)
|
||||
if ctx.Bool("await") {
|
||||
res, err = a.WaitAny(gctx, resVub, txHash, resHash)
|
||||
if err != nil {
|
||||
if errors.Is(err, waiter.ErrTxNotAccepted) {
|
||||
if mainTx == nil {
|
||||
return cli.NewExitError(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1)
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of both target and conflicting transactions). Main transaction is not valid anymore, cancellation is successful\n", resVub)
|
||||
return nil
|
||||
}
|
||||
return cli.NewExitError(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1)
|
||||
}
|
||||
if txHash.Equals(res.Container) {
|
||||
fmt.Fprintln(ctx.App.Writer, "Target transaction accepted")
|
||||
acceptedH = txHash
|
||||
} else {
|
||||
fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted")
|
||||
}
|
||||
}
|
||||
txctx.DumpTransactionInfo(ctx.App.Writer, acceptedH, res)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,12 +17,14 @@ import (
|
|||
// NewCommands returns util commands for neo-go CLI.
|
||||
func NewCommands() []cli.Command {
|
||||
txDumpFlags := append([]cli.Flag{}, options.RPC...)
|
||||
txSendFlags := append(txDumpFlags, txctx.AwaitFlag)
|
||||
txCancelFlags := append([]cli.Flag{
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "address to use as conflicting transaction signee (and gas source)",
|
||||
},
|
||||
txctx.GasFlag,
|
||||
txctx.AwaitFlag,
|
||||
}, options.RPC...)
|
||||
txCancelFlags = append(txCancelFlags, options.Wallet...)
|
||||
return []cli.Command{
|
||||
|
@ -42,19 +44,20 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "sendtx",
|
||||
Usage: "Send complete transaction stored in a context file",
|
||||
UsageText: "sendtx [-r <endpoint>] <file.in>",
|
||||
UsageText: "sendtx [-r <endpoint>] <file.in> [--await]",
|
||||
Description: `Sends the transaction from the given context file to the given RPC node if it's
|
||||
completely signed and ready. This command expects a ContractParametersContext
|
||||
JSON file for input, it can't handle binary (or hex- or base64-encoded)
|
||||
transactions.
|
||||
transactions. If the --await flag is included, the command waits for the
|
||||
transaction to be included in a block before exiting.
|
||||
`,
|
||||
Action: sendTx,
|
||||
Flags: txDumpFlags,
|
||||
Flags: txSendFlags,
|
||||
},
|
||||
{
|
||||
Name: "canceltx",
|
||||
Usage: "Cancel transaction by sending conflicting transaction",
|
||||
UsageText: "canceltx <txid> -r <endpoint> --wallet <wallet> [--account <account>] [--wallet-config <path>] [--gas <gas>]",
|
||||
UsageText: "canceltx <txid> -r <endpoint> --wallet <wallet> [--account <account>] [--wallet-config <path>] [--gas <gas>] [--await]",
|
||||
Description: `Aims to prevent a transaction from being added to the blockchain by dispatching a more
|
||||
prioritized conflicting transaction to the specified RPC node. The input for this command should
|
||||
be the transaction hash. If another account is not specified, the conflicting transaction is
|
||||
|
@ -65,7 +68,8 @@ func NewCommands() []cli.Command {
|
|||
target transaction's ValidUntilBlock value. If the target transaction is not in the memory pool, standard
|
||||
NetworkFee calculations are performed based on the calculatenetworkfee RPC request. If the --gas
|
||||
flag is included, the specified value is added to the resulting conflicting transaction network fee
|
||||
in both scenarios.`,
|
||||
in both scenarios. When the --await flag is included, the command waits for one of the conflicting
|
||||
or target transactions to be included in a block.`,
|
||||
Action: cancelTx,
|
||||
Flags: txCancelFlags,
|
||||
},
|
||||
|
@ -75,6 +79,11 @@ func NewCommands() []cli.Command {
|
|||
UsageText: "txdump [-r <endpoint>] <file.in>",
|
||||
Action: txDump,
|
||||
Flags: txDumpFlags,
|
||||
Description: `Dumps the transaction from the given parameter context file to
|
||||
the output. This command expects a ContractParametersContext JSON file for input, it can't handle
|
||||
binary (or hex- or base64-encoded) transactions. If --rpc-endpoint flag is specified the result
|
||||
of the given script after running it true the VM will be printed. Otherwise only transaction will
|
||||
be printed.`,
|
||||
},
|
||||
{
|
||||
Name: "ops",
|
||||
|
|
|
@ -5,6 +5,9 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/cli/txctx"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -37,6 +40,14 @@ func sendTx(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
var aer *state.AppExecResult
|
||||
if ctx.Bool("await") {
|
||||
version, err := c.GetVersion()
|
||||
aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1)
|
||||
}
|
||||
}
|
||||
txctx.DumpTransactionInfo(ctx.App.Writer, res, aer)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -136,3 +136,44 @@ func TestUtilCancelTx(t *testing.T) {
|
|||
return aerErr == nil
|
||||
}, time.Second*2, time.Millisecond*50)
|
||||
}
|
||||
|
||||
func TestAwaitUtilCancelTx(t *testing.T) {
|
||||
e := testcli.NewExecutor(t, true)
|
||||
|
||||
w, err := wallet.NewWalletFromFile("../testdata/testwallet.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
transferArgs := []string{
|
||||
"neo-go", "wallet", "nep17", "transfer",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--to", w.Accounts[0].Address,
|
||||
"--token", "NEO",
|
||||
"--from", testcli.ValidatorAddr,
|
||||
"--force",
|
||||
}
|
||||
args := []string{"neo-go", "util", "canceltx",
|
||||
"-r", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--address", testcli.ValidatorAddr,
|
||||
"--await"}
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, append(transferArgs, "--amount", "1")...)
|
||||
line := e.GetNextLine(t)
|
||||
txHash, err := util.Uint256DecodeStringLE(line)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := e.Chain.GetMemPool().TryGetValue(txHash)
|
||||
require.True(t, ok)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, append(args, txHash.StringLE())...)
|
||||
e.CheckNextLine(t, "Conflicting transaction accepted")
|
||||
resHash, _ := e.CheckAwaitableTxPersisted(t)
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
_, aerErr := e.Chain.GetAppExecResults(resHash.Hash(), trigger.Application)
|
||||
return aerErr == nil
|
||||
}, time.Second*2, time.Millisecond*50)
|
||||
}
|
||||
|
|
|
@ -159,4 +159,61 @@ func TestRegisterCandidate(t *testing.T) {
|
|||
e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress, validatorAddress)
|
||||
e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something")
|
||||
e.RunWithError(t, "neo-go", "query", "candidates", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something")
|
||||
|
||||
t.Run("VoteUnvote await", func(t *testing.T) {
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "register",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--address", validatorAddress,
|
||||
"--force", "--await")
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "vote",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--address", validatorAddress,
|
||||
"--candidate", validatorHex,
|
||||
"--force",
|
||||
"--await")
|
||||
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
b, _ := e.Chain.GetGoverningTokenBalance(testcli.ValidatorPriv.GetScriptHash())
|
||||
|
||||
// unvote
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "vote",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--address", validatorAddress,
|
||||
"--force", "--await")
|
||||
_, index := e.CheckAwaitableTxPersisted(t)
|
||||
|
||||
vs, err = e.Chain.GetEnrollments()
|
||||
require.Equal(t, 1, len(vs))
|
||||
require.Equal(t, validatorPublic, vs[0].Key)
|
||||
require.Equal(t, big.NewInt(0), vs[0].Votes)
|
||||
|
||||
// check state
|
||||
e.Run(t, "neo-go", "query", "voter",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
validatorAddress)
|
||||
e.CheckNextLine(t, "^\\s*Voted:\\s+"+"null") // no vote.
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$")
|
||||
e.CheckNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||
e.CheckEOF(t)
|
||||
})
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "candidate", "unregister",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--address", validatorAddress,
|
||||
"--force",
|
||||
"--await")
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
|
||||
vs, err = e.Chain.GetEnrollments()
|
||||
require.Equal(t, 0, len(vs))
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/cli/txctx"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -17,6 +20,7 @@ func signStoredTransaction(ctx *cli.Context) error {
|
|||
out = ctx.String("out")
|
||||
rpcNode = ctx.String(options.RPCEndpointFlag)
|
||||
addrFlag = ctx.Generic("address").(*flags.Address)
|
||||
aer *state.AppExecResult
|
||||
)
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
|
@ -84,10 +88,15 @@ func signStoredTransaction(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, res.StringLE())
|
||||
return nil
|
||||
if ctx.Bool("await") {
|
||||
version, err := c.GetVersion()
|
||||
aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
|
||||
txctx.DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ func newNEP11Commands() []cli.Command {
|
|||
{
|
||||
Name: "transfer",
|
||||
Usage: "transfer NEP-11 tokens",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [--await] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
Action: transferNEP11,
|
||||
Flags: transferFlags,
|
||||
Description: `Transfers specified NEP-11 token with optional cosigners list attached to
|
||||
|
@ -110,7 +110,8 @@ func newNEP11Commands() []cli.Command {
|
|||
'contract testinvokefunction' documentation for the details
|
||||
about cosigners syntax. If no cosigners are given then the
|
||||
sender with CalledByEntry scope will be used as the only
|
||||
signer.
|
||||
signer. If --await flag is set then the command will wait
|
||||
for the transaction to be included in a block.
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -70,6 +70,7 @@ var (
|
|||
txctx.GasFlag,
|
||||
txctx.SysGasFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
cli.StringFlag{
|
||||
Name: "amount",
|
||||
Usage: "Amount of asset to send",
|
||||
|
@ -83,6 +84,7 @@ var (
|
|||
txctx.GasFlag,
|
||||
txctx.SysGasFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
}, options.RPC...)
|
||||
)
|
||||
|
||||
|
@ -147,20 +149,22 @@ func newNEP17Commands() []cli.Command {
|
|||
{
|
||||
Name: "transfer",
|
||||
Usage: "transfer NEP-17 tokens",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
Action: transferNEP17,
|
||||
Flags: transferFlags,
|
||||
Description: `Transfers specified NEP-17 token amount with optional 'data' parameter and cosigners
|
||||
list attached to the transfer. See 'contract testinvokefunction' documentation
|
||||
for the details about 'data' parameter and cosigners syntax. If no 'data' is
|
||||
given then default nil value will be used. If no cosigners are given then the
|
||||
sender with CalledByEntry scope will be used as the only signer.
|
||||
sender with CalledByEntry scope will be used as the only signer. When --await
|
||||
flag is used, the command waits for the transaction to be included in a block
|
||||
before exiting.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "multitransfer",
|
||||
Usage: "transfer NEP-17 tokens to multiple recipients",
|
||||
UsageText: `multitransfer -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --from <addr>` +
|
||||
UsageText: `multitransfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr>` +
|
||||
` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]] [-- <cosigner1:Scope> [<cosigner2> [...]]]`,
|
||||
Action: multiTransferNEP17,
|
||||
Flags: multiTransferFlags,
|
||||
|
|
|
@ -20,7 +20,7 @@ func newValidatorCommands() []cli.Command {
|
|||
{
|
||||
Name: "register",
|
||||
Usage: "register as a new candidate",
|
||||
UsageText: "register -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force]",
|
||||
UsageText: "register -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
|
||||
Action: handleRegister,
|
||||
Flags: append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
|
@ -29,6 +29,7 @@ func newValidatorCommands() []cli.Command {
|
|||
txctx.SysGasFlag,
|
||||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to register",
|
||||
|
@ -38,7 +39,7 @@ func newValidatorCommands() []cli.Command {
|
|||
{
|
||||
Name: "unregister",
|
||||
Usage: "unregister self as a candidate",
|
||||
UsageText: "unregister -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force]",
|
||||
UsageText: "unregister -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
|
||||
Action: handleUnregister,
|
||||
Flags: append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
|
@ -47,6 +48,7 @@ func newValidatorCommands() []cli.Command {
|
|||
txctx.SysGasFlag,
|
||||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to unregister",
|
||||
|
@ -56,9 +58,10 @@ func newValidatorCommands() []cli.Command {
|
|||
{
|
||||
Name: "vote",
|
||||
Usage: "vote for a validator",
|
||||
UsageText: "vote -w <path> -r <rpc> [-s <timeout>] [-g gas] [-e sysgas] -a <addr> [-c <public key>] [--out file] [--force]",
|
||||
UsageText: "vote -w <path> -r <rpc> [-s <timeout>] [-g gas] [-e sysgas] -a <addr> [-c <public key>] [--out file] [--force] [--await]",
|
||||
Description: `Votes for a validator by calling "vote" method of a NEO native
|
||||
contract. Do not provide candidate argument to perform unvoting.
|
||||
contract. Do not provide candidate argument to perform unvoting. If --await flag is
|
||||
included, the command waits for the transaction to be included in a block before exiting.
|
||||
`,
|
||||
Action: handleVote,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -68,6 +71,7 @@ func newValidatorCommands() []cli.Command {
|
|||
txctx.SysGasFlag,
|
||||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to vote from",
|
||||
|
|
|
@ -86,6 +86,7 @@ func NewCommands() []cli.Command {
|
|||
txctx.SysGasFlag,
|
||||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to claim GAS for",
|
||||
|
@ -96,6 +97,7 @@ func NewCommands() []cli.Command {
|
|||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
txctx.OutFlag,
|
||||
txctx.AwaitFlag,
|
||||
inFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
|
@ -110,7 +112,7 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "claim",
|
||||
Usage: "claim GAS",
|
||||
UsageText: "neo-go wallet claim -w wallet [--wallet-config path] [-g gas] [-e sysgas] -a address -r endpoint [-s timeout] [--out file] [--force]",
|
||||
UsageText: "neo-go wallet claim -w wallet [--wallet-config path] [-g gas] [-e sysgas] -a address -r endpoint [-s timeout] [--out file] [--force] [--await]",
|
||||
Action: claimGas,
|
||||
Flags: claimFlags,
|
||||
},
|
||||
|
@ -288,13 +290,15 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "sign",
|
||||
Usage: "cosign transaction with multisig/contract/additional account",
|
||||
UsageText: "sign -w wallet [--wallet-config path] --address <address> --in <file.in> [--out <file.out>] [-r <endpoint>]",
|
||||
UsageText: "sign -w wallet [--wallet-config path] --address <address> --in <file.in> [--out <file.out>] [-r <endpoint>] [--await]",
|
||||
Description: `Signs the given (in file.in) context (which must be a transaction
|
||||
signing context) for the given address using the given wallet. This command can
|
||||
output the resulting JSON (with additional signature added) right to the console
|
||||
(if no file.out and no RPC endpoint specified) or into a file (which can be the
|
||||
same as input one). If an RPC endpoint is given it'll also try to construct a
|
||||
complete transaction and send it via RPC (printing its hash if everything is OK).
|
||||
complete transaction and send it via RPC (printing its hash if everything is OK).
|
||||
If the --await (with a given RPC endpoint) flag is included, the command waits
|
||||
for the transaction to be included in a block before exiting.
|
||||
`,
|
||||
Action: signStoredTransaction,
|
||||
Flags: signFlags,
|
||||
|
|
|
@ -605,6 +605,47 @@ func TestWalletClaimGas(t *testing.T) {
|
|||
} else {
|
||||
require.Equal(t, 1, balanceAfter.Cmp(balanceBefore))
|
||||
}
|
||||
t.Run("await", func(t *testing.T) {
|
||||
args := []string{
|
||||
"neo-go", "wallet", "nep17", "multitransfer",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--from", testcli.ValidatorAddr, "--await",
|
||||
"--force",
|
||||
"NEO:" + testcli.TestWalletAccount + ":1000",
|
||||
"GAS:" + testcli.TestWalletAccount + ":1000", // for tx send
|
||||
}
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, args...)
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
|
||||
h, err := address.StringToUint160(testcli.TestWalletAccount)
|
||||
require.NoError(t, err)
|
||||
|
||||
balanceBefore := e.Chain.GetUtilityTokenBalance(h)
|
||||
claimHeight := e.Chain.BlockHeight() + 1
|
||||
cl, err := e.Chain.CalculateClaimable(h, claimHeight)
|
||||
require.NoError(t, err)
|
||||
require.True(t, cl.Sign() > 0)
|
||||
|
||||
e.In.WriteString("testpass\r")
|
||||
e.Run(t, "neo-go", "wallet", "claim",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.TestWalletPath,
|
||||
"--address", testcli.TestWalletAccount,
|
||||
"--force", "--await")
|
||||
tx, height = e.CheckAwaitableTxPersisted(t)
|
||||
balanceBefore.Sub(balanceBefore, big.NewInt(tx.NetworkFee+tx.SystemFee))
|
||||
balanceBefore.Add(balanceBefore, cl)
|
||||
|
||||
balanceAfter = e.Chain.GetUtilityTokenBalance(h)
|
||||
// height can be bigger than claimHeight especially when tests are executed with -race.
|
||||
if height == claimHeight {
|
||||
require.Equal(t, 0, balanceAfter.Cmp(balanceBefore))
|
||||
} else {
|
||||
require.Equal(t, 1, balanceAfter.Cmp(balanceBefore))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalletImportDeployed(t *testing.T) {
|
||||
|
@ -822,6 +863,31 @@ func TestOfflineSigning(t *testing.T) {
|
|||
"--in", txPath)
|
||||
})
|
||||
e.CheckTxPersisted(t)
|
||||
|
||||
t.Run("await 1/1 multisig", func(t *testing.T) {
|
||||
args := []string{"neo-go", "wallet", "nep17", "transfer",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", walletPath,
|
||||
"--from", testcli.ValidatorAddr,
|
||||
"--to", w.Accounts[0].Address,
|
||||
"--token", "NEO",
|
||||
"--amount", "1",
|
||||
"--force",
|
||||
}
|
||||
e.Run(t, append(args, "--out", txPath)...)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "sign",
|
||||
"--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
|
||||
"--in", txPath, "--out", txPath)
|
||||
|
||||
e.Run(t, "neo-go", "wallet", "sign",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", walletPath, "--address", testcli.ValidatorAddr,
|
||||
"--in", txPath, "--await")
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
})
|
||||
|
||||
t.Run("simple signature", func(t *testing.T) {
|
||||
simpleAddr := w.Accounts[0].Address
|
||||
args := []string{"neo-go", "wallet", "nep17", "transfer",
|
||||
|
@ -853,6 +919,31 @@ func TestOfflineSigning(t *testing.T) {
|
|||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
txPath)
|
||||
})
|
||||
|
||||
t.Run("await simple signature", func(t *testing.T) {
|
||||
simpleAddr := w.Accounts[0].Address
|
||||
args := []string{"neo-go", "wallet", "nep17", "transfer",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", walletPath,
|
||||
"--from", simpleAddr,
|
||||
"--to", testcli.ValidatorAddr,
|
||||
"--token", "NEO",
|
||||
"--amount", "1",
|
||||
"--force",
|
||||
}
|
||||
|
||||
e.Run(t, append(args, "--out", txPath)...)
|
||||
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, "neo-go", "wallet", "sign",
|
||||
"--wallet", testcli.ValidatorWallet, "--address", simpleAddr,
|
||||
"--in", txPath, "--out", txPath)
|
||||
|
||||
e.Run(t, "neo-go", "util", "sendtx",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
txPath, "--await")
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalletDump(t *testing.T) {
|
||||
|
|
|
@ -309,6 +309,13 @@ func (e *Executor) CheckTxPersisted(t *testing.T, prefix ...string) (*transactio
|
|||
return tx, height
|
||||
}
|
||||
|
||||
func (e *Executor) CheckAwaitableTxPersisted(t *testing.T, prefix ...string) (*transaction.Transaction, uint32) {
|
||||
tx, vub := e.CheckTxPersisted(t, prefix...)
|
||||
e.CheckNextLine(t, "OnChain:\ttrue")
|
||||
e.CheckNextLine(t, "VMState:\tHALT")
|
||||
return tx, vub
|
||||
}
|
||||
|
||||
func GenerateKeys(t *testing.T, n int) ([]*keys.PrivateKey, keys.PublicKeys) {
|
||||
privs := make([]*keys.PrivateKey, n)
|
||||
pubs := make(keys.PublicKeys, n)
|
||||
|
|
Loading…
Reference in a new issue