From c2c10c111c00f1639adf4c9eda8fc64c8dd278d9 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 31 Aug 2022 15:43:30 +0300 Subject: [PATCH] cli/wallet: process non-out non-rpc calls to sign And document the behavior better. Fixes #2664. --- cli/multisig_test.go | 41 +++++++++++++++++++++++++++++++++++------ cli/wallet/multisig.go | 35 +++++++++++++++++++++++++---------- cli/wallet/wallet.go | 13 ++++++++++--- 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 7bf903c86..742822940 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -14,6 +14,7 @@ import ( "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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/context" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" "github.com/stretchr/testify/require" @@ -162,13 +163,41 @@ func TestSignMultisigTx(t *testing.T) { require.Equal(t, vmstate.Halt.String(), res.State, res.FaultException) }) - e.In.WriteString("pass\r") - e.Run(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", wallet2Path, "--address", multisigAddr, - "--in", txPath, "--out", txPath) - e.checkTxPersisted(t) + t.Run("console output", func(t *testing.T) { + oldIn, err := os.ReadFile(txPath) + require.NoError(t, err) + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath) + newIn, err := os.ReadFile(txPath) + require.NoError(t, err) + require.Equal(t, oldIn, newIn) + pcOld := new(context.ParameterContext) + require.NoError(t, json.Unmarshal(oldIn, pcOld)) + + jOut := e.Out.Bytes() + pcNew := new(context.ParameterContext) + require.NoError(t, json.Unmarshal(jOut, pcNew)) + + require.Equal(t, pcOld.Type, pcNew.Type) + require.Equal(t, pcOld.Network, pcNew.Network) + require.Equal(t, pcOld.Verifiable, pcNew.Verifiable) + require.Equal(t, pcOld.Items[multisigHash].Script, pcNew.Items[multisigHash].Script) + // It's completely signed after this, so parameters have signatures now as well. + require.NotEqual(t, pcOld.Items[multisigHash].Parameters, pcNew.Items[multisigHash].Parameters) + require.NotEqual(t, pcOld.Items[multisigHash].Signatures, pcNew.Items[multisigHash].Signatures) + }) + + t.Run("sign, save and send", func(t *testing.T) { + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath, "--out", txPath) + e.checkTxPersisted(t) + }) t.Run("double-sign", func(t *testing.T) { e.In.WriteString("pass\r") e.RunWithError(t, "neo-go", "wallet", "sign", diff --git a/cli/wallet/multisig.go b/cli/wallet/multisig.go index 35f1a7db7..8807977d1 100644 --- a/cli/wallet/multisig.go +++ b/cli/wallet/multisig.go @@ -1,6 +1,7 @@ package wallet import ( + "encoding/json" "fmt" "github.com/nspcc-dev/neo-go/cli/cmdargs" @@ -12,6 +13,11 @@ import ( ) func signStoredTransaction(ctx *cli.Context) error { + var ( + out = ctx.String("out") + rpcNode = ctx.String(options.RPCEndpointFlag) + addrFlag = ctx.Generic("address").(*flags.Address) + ) if err := cmdargs.EnsureNone(ctx); err != nil { return err } @@ -20,11 +26,11 @@ func signStoredTransaction(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - c, err := paramcontext.Read(ctx.String("in")) + pc, err := paramcontext.Read(ctx.String("in")) if err != nil { return cli.NewExitError(err, 1) } - addrFlag := ctx.Generic("address").(*flags.Address) + if !addrFlag.IsSet { return cli.NewExitError("address was not provided", 1) } @@ -35,7 +41,7 @@ func signStoredTransaction(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - tx, ok := c.Verifiable.(*transaction.Transaction) + tx, ok := pc.Verifiable.(*transaction.Transaction) if !ok { return cli.NewExitError("verifiable item is not a transaction", 1) } @@ -52,18 +58,27 @@ func signStoredTransaction(ctx *cli.Context) error { } priv := acc.PrivateKey() - sign := priv.SignHashable(uint32(c.Network), tx) - if err := c.AddSignature(ch, acc.Contract, priv.PublicKey(), sign); err != nil { + sign := priv.SignHashable(uint32(pc.Network), tx) + if err := pc.AddSignature(ch, acc.Contract, priv.PublicKey(), sign); err != nil { return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1) } - if out := ctx.String("out"); out != "" { - if err := paramcontext.Save(c, out); err != nil { - return cli.NewExitError(fmt.Errorf("failed to dump resulting transaction: %w", err), 1) + // Not saving and not sending, print. + if out == "" && rpcNode == "" { + txt, err := json.MarshalIndent(pc, " ", " ") + if err != nil { + return cli.NewExitError(fmt.Errorf("can't display resulting context: %w", err), 1) + } + fmt.Fprintln(ctx.App.Writer, string(txt)) + return nil + } + if out != "" { + if err := paramcontext.Save(pc, out); err != nil { + return cli.NewExitError(fmt.Errorf("can't save resulting context: %w", err), 1) } } - if len(ctx.String(options.RPCEndpointFlag)) != 0 { + if rpcNode != "" { for i := range tx.Signers { - w, err := c.GetWitness(tx.Signers[i].Account) + w, err := pc.GetWitness(tx.Signers[i].Account) if err != nil { return cli.NewExitError(fmt.Errorf("failed to construct witness for signer #%d: %w", i, err), 1) } diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 42640245c..c74d358dd 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -281,9 +281,16 @@ func NewCommands() []cli.Command { { Name: "sign", Usage: "cosign transaction with multisig/contract/additional account", - UsageText: "sign -w wallet [--wallet-config path] --address
--in --out [-r ]", - Action: signStoredTransaction, - Flags: signFlags, + UsageText: "sign -w wallet [--wallet-config path] --address
--in [--out ] [-r ]", + 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). +`, + Action: signStoredTransaction, + Flags: signFlags, }, { Name: "nep17",