cli: deduplicate smartcontract/wallet tx confirm/sign/save/send

It's the same code.
This commit is contained in:
Roman Khimov 2022-10-06 22:59:47 +03:00
parent 5200765dab
commit 0efe3dd42c
6 changed files with 129 additions and 150 deletions

View file

@ -8,19 +8,17 @@ 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"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/cli/txctx"
cliwallet "github.com/nspcc-dev/neo-go/cli/wallet" cliwallet "github.com/nspcc-dev/neo-go/cli/wallet"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/core/state" "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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"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/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
@ -62,22 +60,6 @@ var (
Name: addressFlagName, Name: addressFlagName,
Usage: "address to use as transaction signee (and gas source)", Usage: "address to use as transaction signee (and gas source)",
} }
gasFlag = flags.Fixed8Flag{
Name: "gas, g",
Usage: "network fee to add to the transaction (prioritizing it)",
}
sysGasFlag = flags.Fixed8Flag{
Name: "sysgas, e",
Usage: "system fee to add to transaction (compensating for execution)",
}
outFlag = cli.StringFlag{
Name: "out",
Usage: "file to put JSON transaction to",
}
forceFlag = cli.BoolFlag{
Name: "force",
Usage: "force-push the transaction in case of bad VM state after test script invocation",
}
) )
// ModVersion contains `pkg/interop` module version // ModVersion contains `pkg/interop` module version
@ -120,10 +102,10 @@ func NewCommands() []cli.Command {
walletFlag, walletFlag,
walletConfigFlag, walletConfigFlag,
addressFlag, addressFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
outFlag, txctx.OutFlag,
forceFlag, txctx.ForceFlag,
} }
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
deployFlags := append(invokeFunctionFlags, []cli.Flag{ deployFlags := append(invokeFunctionFlags, []cli.Flag{
@ -668,7 +650,6 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []interface{}, cosigners []transaction.Signer) error { func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []interface{}, cosigners []transaction.Signer) error {
var ( var (
err error err error
gas, sysgas fixedn.Fixed8
signersAccounts []actor.SignerAccount signersAccounts []actor.SignerAccount
resp *result.Invoke resp *result.Invoke
signAndPush = acc != nil signAndPush = acc != nil
@ -676,8 +657,6 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
act *actor.Actor act *actor.Actor
) )
if signAndPush { if signAndPush {
gas = flags.Fixed8FromContext(ctx, "gas")
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None) signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
@ -731,44 +710,16 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
} }
fmt.Fprintln(ctx.App.Writer, string(b)) fmt.Fprintln(ctx.App.Writer, string(b))
} else { return nil
}
if len(resp.Script) == 0 { if len(resp.Script) == 0 {
return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) return cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
} }
ver := act.GetVersion() tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed, nil)
tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed+int64(sysgas), nil)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
} }
tx.NetworkFee += int64(gas) return txctx.SignAndSend(ctx, act, acc, tx)
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 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 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 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 nil
} }
func testInvokeScript(ctx *cli.Context) error { func testInvokeScript(ctx *cli.Context) error {

79
cli/txctx/tx.go Normal file
View file

@ -0,0 +1,79 @@
/*
Package txctx contains helper functions that deal with transactions in CLI context.
*/
package txctx
import (
"fmt"
"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/transaction"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli"
)
var (
// GasFlag is a flag used for the additional network fee.
GasFlag = flags.Fixed8Flag{
Name: "gas, g",
Usage: "network fee to add to the transaction (prioritizing it)",
}
// SysGasFlag is a flag used for the additional system fee.
SysGasFlag = flags.Fixed8Flag{
Name: "sysgas, e",
Usage: "system fee to add to the transaction (compensating for execution)",
}
// OutFlag is a flag used for file output.
OutFlag = cli.StringFlag{
Name: "out",
Usage: "file (JSON) to put signature context with a transaction to",
}
// ForceFlag is a flag used to force transaction send.
ForceFlag = cli.BoolFlag{
Name: "force",
Usage: "Do not ask for a confirmation (and ignore errors)",
}
)
// SignAndSend adds network and system fees to the provided transaction and
// either sends it to the network (with a confirmation or --force flag) or saves
// it into a file (given in the --out flag).
func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *transaction.Transaction) error {
var (
err error
gas = flags.Fixed8FromContext(ctx, "gas")
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
ver = act.GetVersion()
)
tx.SystemFee += int64(sysgas)
tx.NetworkFee += int64(gas)
if outFile := ctx.String("out"); outFile != "" {
// Make a long-lived transaction, it's to be signed manually.
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
err = paramcontext.InitAndSave(ver.Protocol.Network, tx, acc, outFile)
} else {
if !ctx.Bool("force") {
promptTime := time.Now()
err := input.ConfirmTx(ctx.App.Writer, tx)
if err != nil {
return cli.NewExitError(err, 1)
}
waitTime := time.Since(promptTime)
// Compensate for confirmation waiting.
tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1
}
_, _, err = act.SignAndSend(tx)
}
if err != nil {
return cli.NewExitError(err, 1)
}
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
return nil
}

View file

@ -9,6 +9,7 @@ import (
"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"
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/cli/txctx"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/neorpc/result"
@ -94,7 +95,7 @@ func newNEP11Commands() []cli.Command {
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
tokenFlag, tokenFlag,
forceFlag, txctx.ForceFlag,
}, },
}, },
{ {

View file

@ -6,13 +6,11 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"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"
"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/cli/options" "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/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"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/encoding/address"
@ -37,14 +35,6 @@ var (
Name: "token", Name: "token",
Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))", Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))",
} }
gasFlag = flags.Fixed8Flag{
Name: "gas, g",
Usage: "network fee to add to the transaction (prioritizing it)",
}
sysGasFlag = flags.Fixed8Flag{
Name: "sysgas, e",
Usage: "system fee to add to transaction (compensating for execution)",
}
baseBalanceFlags = []cli.Flag{ baseBalanceFlags = []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
@ -65,13 +55,13 @@ var (
baseTransferFlags = []cli.Flag{ baseTransferFlags = []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
outFlag, txctx.OutFlag,
fromAddrFlag, fromAddrFlag,
toAddrFlag, toAddrFlag,
tokenFlag, tokenFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
forceFlag, txctx.ForceFlag,
cli.StringFlag{ cli.StringFlag{
Name: "amount", Name: "amount",
Usage: "Amount of asset to send", Usage: "Amount of asset to send",
@ -80,11 +70,11 @@ var (
multiTransferFlags = append([]cli.Flag{ multiTransferFlags = append([]cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
outFlag, txctx.OutFlag,
fromAddrFlag, fromAddrFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
forceFlag, txctx.ForceFlag,
}, options.RPC...) }, options.RPC...)
) )
@ -143,7 +133,7 @@ func newNEP17Commands() []cli.Command {
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
tokenFlag, tokenFlag,
forceFlag, txctx.ForceFlag,
}, },
}, },
{ {
@ -599,7 +589,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
} }
return signAndSendSomeTransaction(ctx, act, acc, tx) return txctx.SignAndSend(ctx, act, acc, tx)
} }
func transferNEP17(ctx *cli.Context) error { func transferNEP17(ctx *cli.Context) error {
@ -697,7 +687,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
} }
return signAndSendSomeTransaction(ctx, act, acc, tx) return txctx.SignAndSend(ctx, act, acc, tx)
} }
func makeMultiTransferNEP17(act *actor.Actor, recipients []rpcclient.TransferTarget) (*transaction.Transaction, error) { func makeMultiTransferNEP17(act *actor.Actor, recipients []rpcclient.TransferTarget) (*transaction.Transaction, error) {
@ -713,42 +703,6 @@ func makeMultiTransferNEP17(act *actor.Actor, recipients []rpcclient.TransferTar
return act.MakeUnsignedRun(script, nil) return act.MakeUnsignedRun(script, nil)
} }
func signAndSendSomeTransaction(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *transaction.Transaction) error {
var (
err error
gas = flags.Fixed8FromContext(ctx, "gas")
sysgas = flags.Fixed8FromContext(ctx, "sysgas")
)
tx.SystemFee += int64(sysgas)
tx.NetworkFee += int64(gas)
ver := act.GetVersion()
if outFile := ctx.String("out"); outFile != "" {
// Make a long-lived transaction, it's to be signed manually.
tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2
err = paramcontext.InitAndSave(ver.Protocol.Network, tx, acc, outFile)
} else {
if !ctx.Bool("force") {
promptTime := time.Now()
err := input.ConfirmTx(ctx.App.Writer, tx)
if err != nil {
return cli.NewExitError(err, 1)
}
waitTime := time.Since(promptTime)
// Compensate for confirmation waiting.
tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1
}
_, _, err = act.SignAndSend(tx)
}
if err != nil {
return cli.NewExitError(err, 1)
}
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
return nil
}
func getDefaultAddress(fromFlag *flags.Address, w *wallet.Wallet) (util.Uint160, error) { func getDefaultAddress(fromFlag *flags.Address, w *wallet.Wallet) (util.Uint160, error) {
if fromFlag.IsSet { if fromFlag.IsSet {
return fromFlag.Uint160(), nil return fromFlag.Uint160(), nil

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/cli/txctx"
"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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -27,10 +28,10 @@ func newValidatorCommands() []cli.Command {
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
outFlag, txctx.OutFlag,
forceFlag, txctx.ForceFlag,
flags.AddressFlag{ flags.AddressFlag{
Name: "address, a", Name: "address, a",
Usage: "Address to register", Usage: "Address to register",
@ -45,10 +46,10 @@ func newValidatorCommands() []cli.Command {
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
outFlag, txctx.OutFlag,
forceFlag, txctx.ForceFlag,
flags.AddressFlag{ flags.AddressFlag{
Name: "address, a", Name: "address, a",
Usage: "Address to unregister", Usage: "Address to unregister",
@ -66,10 +67,10 @@ func newValidatorCommands() []cli.Command {
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
outFlag, txctx.OutFlag,
forceFlag, txctx.ForceFlag,
flags.AddressFlag{ flags.AddressFlag{
Name: "address, a", Name: "address, a",
Usage: "Address to vote from", Usage: "Address to vote from",
@ -132,7 +133,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
return signAndSendSomeTransaction(ctx, act, acc, tx) return txctx.SignAndSend(ctx, act, acc, tx)
} }
func handleVote(ctx *cli.Context) error { func handleVote(ctx *cli.Context) error {

View file

@ -14,6 +14,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/cli/txctx"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"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/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -64,10 +65,6 @@ var (
Name: "decrypt, d", Name: "decrypt, d",
Usage: "Decrypt encrypted keys.", Usage: "Decrypt encrypted keys.",
} }
outFlag = cli.StringFlag{
Name: "out",
Usage: "file to put JSON transaction to",
}
inFlag = cli.StringFlag{ inFlag = cli.StringFlag{
Name: "in", Name: "in",
Usage: "file with JSON transaction", Usage: "file with JSON transaction",
@ -80,10 +77,6 @@ var (
Name: "to", Name: "to",
Usage: "Address to send an asset to", Usage: "Address to send an asset to",
} }
forceFlag = cli.BoolFlag{
Name: "force",
Usage: "Do not ask for a confirmation",
}
) )
// NewCommands returns 'wallet' command. // NewCommands returns 'wallet' command.
@ -91,10 +84,10 @@ func NewCommands() []cli.Command {
claimFlags := []cli.Flag{ claimFlags := []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
gasFlag, txctx.GasFlag,
sysGasFlag, txctx.SysGasFlag,
outFlag, txctx.OutFlag,
forceFlag, txctx.ForceFlag,
flags.AddressFlag{ flags.AddressFlag{
Name: "address, a", Name: "address, a",
Usage: "Address to claim GAS for", Usage: "Address to claim GAS for",
@ -104,7 +97,7 @@ func NewCommands() []cli.Command {
signFlags := []cli.Flag{ signFlags := []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
outFlag, txctx.OutFlag,
inFlag, inFlag,
flags.AddressFlag{ flags.AddressFlag{
Name: "address, a", Name: "address, a",
@ -287,7 +280,7 @@ func NewCommands() []cli.Command {
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
forceFlag, txctx.ForceFlag,
flags.AddressFlag{ flags.AddressFlag{
Name: "address, a", Name: "address, a",
Usage: "Account address or hash in LE form to be removed", Usage: "Account address or hash in LE form to be removed",
@ -322,7 +315,7 @@ func NewCommands() []cli.Command {
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
forceFlag, txctx.ForceFlag,
}, },
}, },
{ {