mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-25 03:56:34 +00:00
Merge pull request #1461 from nspcc-dev/cli/multisig
cli: allow to send multisig deploy/invoke tx
This commit is contained in:
commit
7ddce97f11
5 changed files with 108 additions and 46 deletions
|
@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
53
cli/paramcontext/context.go
Normal file
53
cli/paramcontext/context.go
Normal 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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue