cli: allow to send multisig deploy/invoke tx

It allows to invoke native contracts as committee
from CLI in privnet, e.g. to set new oracle nodes.

Also don't require `out` flag in `multisig sign`
if tx is to be pushed.
This commit is contained in:
Evgenii Stratonikov 2020-10-02 16:13:17 +03:00
parent 0dcf42ac24
commit 897c9198f8
5 changed files with 108 additions and 46 deletions

View file

@ -5,11 +5,13 @@ import (
"math/big"
"os"
"path"
"strings"
"testing"
"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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/stretchr/testify/require"
)
@ -82,4 +84,29 @@ func TestSignMultisigTx(t *testing.T) {
require.Equal(t, big.NewInt(1), b)
b, _ = e.Chain.GetGoverningTokenBalance(multisigHash)
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/input"
"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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -50,6 +51,10 @@ var (
Name: "gas, g",
Usage: "gas to add to the transaction",
}
outFlag = cli.StringFlag{
Name: "out",
Usage: "file to put JSON transaction to",
}
)
const (
@ -104,6 +109,7 @@ func NewCommands() []cli.Command {
walletFlag,
addressFlag,
gasFlag,
outFlag,
}
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
return []cli.Command{{
@ -156,7 +162,7 @@ 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] 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,
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,
@ -481,6 +487,21 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
if err != nil {
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 len(resp.Script) == 0 {
return cli.NewExitError(errors.New("no script returned from the RPC node"), 1)

View file

@ -1,14 +1,12 @@
package wallet
import (
"encoding/json"
"fmt"
"io/ioutil"
"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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/urfave/cli"
)
@ -41,7 +39,7 @@ func signMultisig(ctx *cli.Context) error {
}
defer wall.Close()
c, err := readParameterContext(ctx.String("in"))
c, err := paramcontext.Read(ctx.String("in"))
if err != nil {
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)
}
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)
}
}
@ -95,25 +93,3 @@ func signMultisig(ctx *cli.Context) error {
fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE())
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
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"strings"
"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/pkg/encoding/address"
"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/wallet"
"github.com/urfave/cli"
)
// validUntilBlockIncrement is the number of extra blocks to add to an exported transaction
const validUntilBlockIncrement = 50
var (
neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0)
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 != "" {
// 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 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)
if err := paramcontext.InitAndSave(tx, acc, outFile); err != nil {
return cli.NewExitError(err, 1)
}
} else {
_ = acc.SignTx(tx)