Merge pull request #1461 from nspcc-dev/cli/multisig

cli: allow to send multisig deploy/invoke tx
This commit is contained in:
Roman Khimov 2020-10-07 23:34:52 +03:00 committed by GitHub
commit 7ddce97f11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 108 additions and 46 deletions

View file

@ -5,11 +5,13 @@ import (
"math/big" "math/big"
"os" "os"
"path" "path"
"strings"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"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"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -82,4 +84,29 @@ func TestSignMultisigTx(t *testing.T) {
require.Equal(t, big.NewInt(1), b) require.Equal(t, big.NewInt(1), b)
b, _ = e.Chain.GetGoverningTokenBalance(multisigHash) b, _ = e.Chain.GetGoverningTokenBalance(multisigHash)
require.Equal(t, big.NewInt(3), b) require.Equal(t, big.NewInt(3), b)
t.Run("via invokefunction", func(t *testing.T) {
e.In.WriteString("pass\r")
e.Run(t, "neo-go", "contract", "invokefunction",
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", wallet1Path, "--address", multisigAddr,
"--out", txPath,
client.NeoContractHash.StringLE(), "transfer",
"bytes:"+multisigHash.StringBE(),
"bytes:"+priv.GetScriptHash().StringBE(),
"int:1",
"--", strings.Join([]string{multisigHash.StringLE(), ":", "Global"}, ""))
e.In.WriteString("pass\r")
e.Run(t, "neo-go", "wallet", "multisig", "sign",
"--unittest", "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", wallet2Path, "--address", multisigAddr,
"--in", txPath, "--out", txPath)
e.checkTxPersisted(t)
b, _ := e.Chain.GetGoverningTokenBalance(priv.GetScriptHash())
require.Equal(t, big.NewInt(2), b)
b, _ = e.Chain.GetGoverningTokenBalance(multisigHash)
require.Equal(t, big.NewInt(2), b)
})
} }

View file

@ -0,0 +1,53 @@
package paramcontext
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"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 incompletely signed transaction which can used
// as input to `multisig sign`.
func InitAndSave(tx *transaction.Transaction, acc *wallet.Account, filename string) error {
// avoid fast transaction expiration
tx.ValidUntilBlock += validUntilBlockIncrement
priv := acc.PrivateKey()
pub := priv.PublicKey()
sign := priv.Sign(tx.GetSignedPart())
scCtx := context.NewParameterContext("Neo.Core.ContractTransaction", tx)
if err := scCtx.AddSignature(acc.Contract, pub, sign); err != nil {
return fmt.Errorf("can't add signature: %w", err)
}
return Save(scCtx, filename)
}
// Read reads parameter context from file.
func Read(filename string) (*context.ParameterContext, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("can't read input file: %w", err)
}
c := new(context.ParameterContext)
if err := json.Unmarshal(data, c); err != nil {
return nil, fmt.Errorf("can't parse transaction: %w", err)
}
return c, nil
}
// Save writes parameter context to file.
func Save(c *context.ParameterContext, filename string) error {
if data, err := json.Marshal(c); err != nil {
return fmt.Errorf("can't marshal transaction: %w", err)
} else if err := ioutil.WriteFile(filename, data, 0644); err != nil {
return fmt.Errorf("can't write transaction to file: %w", err)
}
return nil
}

View file

@ -13,6 +13,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/paramcontext"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"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"
@ -50,6 +51,10 @@ var (
Name: "gas, g", Name: "gas, g",
Usage: "gas to add to the transaction", Usage: "gas to add to the transaction",
} }
outFlag = cli.StringFlag{
Name: "out",
Usage: "file to put JSON transaction to",
}
) )
const ( const (
@ -104,6 +109,7 @@ func NewCommands() []cli.Command {
walletFlag, walletFlag,
addressFlag, addressFlag,
gasFlag, gasFlag,
outFlag,
} }
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
return []cli.Command{{ return []cli.Command{{
@ -156,7 +162,7 @@ func NewCommands() []cli.Command {
{ {
Name: "invokefunction", Name: "invokefunction",
Usage: "invoke deployed contract on the blockchain", Usage: "invoke deployed contract on the blockchain",
UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] scripthash [method] [arguments...] [--] [signers...]", UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [--out file] scripthash [method] [arguments...] [--] [signers...]",
Description: `Executes given (as a script hash) deployed script with the given method, 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 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, with None witness scope. If you'd like to change default sender's scope,
@ -481,6 +487,21 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
if out := ctx.String("out"); out != "" {
script, err := hex.DecodeString(resp.Script)
if err != nil {
return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %w", err), 1)
}
tx, err := c.CreateTxFromScript(script, acc, resp.GasConsumed, int64(gas), cosigners...)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
}
if err := paramcontext.InitAndSave(tx, acc, out); err != nil {
return cli.NewExitError(err, 1)
}
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
return nil
}
if signAndPush { if signAndPush {
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)

View file

@ -1,14 +1,12 @@
package wallet package wallet
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"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/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/smartcontract/context"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -41,7 +39,7 @@ func signMultisig(ctx *cli.Context) error {
} }
defer wall.Close() defer wall.Close()
c, err := readParameterContext(ctx.String("in")) c, err := paramcontext.Read(ctx.String("in"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -66,7 +64,7 @@ func signMultisig(ctx *cli.Context) error {
return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1)
} }
if out := ctx.String("out"); out != "" { if out := ctx.String("out"); out != "" {
if err := writeParameterContext(c, out); err != nil { if err := paramcontext.Save(c, out); err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
} }
@ -95,25 +93,3 @@ func signMultisig(ctx *cli.Context) error {
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
return nil return nil
} }
func readParameterContext(filename string) (*context.ParameterContext, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("can't read input file: %w", err)
}
c := new(context.ParameterContext)
if err := json.Unmarshal(data, c); err != nil {
return nil, fmt.Errorf("can't parse transaction: %w", err)
}
return c, nil
}
func writeParameterContext(c *context.ParameterContext, filename string) error {
if data, err := json.Marshal(c); err != nil {
return fmt.Errorf("can't marshal transaction: %w", err)
} else if err := ioutil.WriteFile(filename, data, 0644); err != nil {
return fmt.Errorf("can't write transaction to file: %w", err)
}
return nil
}

View file

@ -1,25 +1,20 @@
package wallet package wallet
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"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/paramcontext"
"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/rpc/client" "github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"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"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// validUntilBlockIncrement is the number of extra blocks to add to an exported transaction
const validUntilBlockIncrement = 50
var ( var (
neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0) neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0)
gasToken = wallet.NewToken(client.GasContractHash, "GAS", "gas", 8) gasToken = wallet.NewToken(client.GasContractHash, "GAS", "gas", 8)
@ -474,18 +469,8 @@ func signAndSendTransfer(ctx *cli.Context, c *client.Client, acc *wallet.Account
} }
if outFile := ctx.String("out"); outFile != "" { if outFile := ctx.String("out"); outFile != "" {
// avoid fast transaction expiration if err := paramcontext.InitAndSave(tx, acc, outFile); err != nil {
tx.ValidUntilBlock += validUntilBlockIncrement return cli.NewExitError(err, 1)
priv := acc.PrivateKey()
pub := priv.PublicKey()
sign := priv.Sign(tx.GetSignedPart())
scCtx := context.NewParameterContext("Neo.Core.ContractTransaction", tx)
if err := scCtx.AddSignature(acc.Contract, pub, sign); err != nil {
return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1)
} else if data, err := json.Marshal(scCtx); err != nil {
return cli.NewExitError(fmt.Errorf("can't marshal tx to JSON: %w", err), 1)
} else if err := ioutil.WriteFile(outFile, data, 0644); err != nil {
return cli.NewExitError(fmt.Errorf("can't write tx to file: %w", err), 1)
} }
} else { } else {
_ = acc.SignTx(tx) _ = acc.SignTx(tx)