Multi-signature workflow support (#3264)
- New keys add --multisig flag to store multisig keys locally. - New multisign command to generate multisig signatures. - New sign --multisig flag to enable multisig mode. - Add multisig transactions support in ante handler. - gaiad add-genesis-account can now take both account addresses and key names. Closes: #3198
This commit is contained in:
committed by
Jack Zampolin
parent
eff1f7ca10
commit
26cb0a125a
@@ -25,6 +25,26 @@ import (
|
||||
stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
func TestGaiaCLIKeysAddMultisig(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
// key names order does not matter
|
||||
f.KeysAdd("msig1", "--multisig-threshold=2",
|
||||
fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz))
|
||||
f.KeysAdd("msig2", "--multisig-threshold=2",
|
||||
fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar))
|
||||
require.Equal(t, f.KeysShow("msig1").Address, f.KeysShow("msig2").Address)
|
||||
|
||||
f.KeysAdd("msig3", "--multisig-threshold=2",
|
||||
fmt.Sprintf("--multisig=%s,%s", keyBar, keyBaz),
|
||||
"--nosort")
|
||||
f.KeysAdd("msig4", "--multisig-threshold=2",
|
||||
fmt.Sprintf("--multisig=%s,%s", keyBaz, keyBar),
|
||||
"--nosort")
|
||||
require.NotEqual(t, f.KeysShow("msig3").Address, f.KeysShow("msig4").Address)
|
||||
}
|
||||
|
||||
func TestGaiaCLIMinimumFees(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
@@ -647,6 +667,180 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) {
|
||||
f.Cleanup()
|
||||
}
|
||||
|
||||
func TestGaiaCLIMultisignInsufficientCosigners(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
proc := f.GDStart()
|
||||
defer proc.Stop(false)
|
||||
|
||||
fooBarBazAddr := f.KeyAddress(keyFooBarBaz)
|
||||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
// Send some tokens from one account to the other
|
||||
success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10))
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Test generate sendTx with multisig
|
||||
success, stdout, _ := f.TxSend(keyFooBarBaz, barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only")
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Sign with foo's key
|
||||
success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String())
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(fooSignatureFile.Name())
|
||||
|
||||
// Multisign, not enough signatures
|
||||
success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{fooSignatureFile.Name()})
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Validate the multisignature
|
||||
success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures", "--json")
|
||||
require.False(t, success)
|
||||
|
||||
// Broadcast the transaction
|
||||
success, _, _ = f.TxBroadcast(signedTxFile.Name())
|
||||
require.False(t, success)
|
||||
}
|
||||
|
||||
func TestGaiaCLIMultisignSortSignatures(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
proc := f.GDStart()
|
||||
defer proc.Stop(false)
|
||||
|
||||
fooBarBazAddr := f.KeyAddress(keyFooBarBaz)
|
||||
barAddr := f.KeyAddress(keyBar)
|
||||
|
||||
// Send some tokens from one account to the other
|
||||
success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10))
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure account balances match expected
|
||||
fooBarBazAcc := f.QueryAccount(fooBarBazAddr)
|
||||
require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64())
|
||||
|
||||
// Test generate sendTx with multisig
|
||||
success, stdout, _ := f.TxSend(keyFooBarBaz, barAddr, sdk.NewInt64Coin(denom, 5), "--generate-only")
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Sign with foo's key
|
||||
success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String())
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(fooSignatureFile.Name())
|
||||
|
||||
// Sign with baz's key
|
||||
success, stdout, _ = f.TxSign(keyBaz, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String())
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
bazSignatureFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(bazSignatureFile.Name())
|
||||
|
||||
// Multisign, keys in different order
|
||||
success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{
|
||||
bazSignatureFile.Name(), fooSignatureFile.Name()})
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Validate the multisignature
|
||||
success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures", "--json")
|
||||
require.True(t, success)
|
||||
|
||||
// Broadcast the transaction
|
||||
success, _, _ = f.TxBroadcast(signedTxFile.Name())
|
||||
require.True(t, success)
|
||||
}
|
||||
|
||||
func TestGaiaCLIMultisign(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
// start gaiad server with minimum fees
|
||||
proc := f.GDStart()
|
||||
defer proc.Stop(false)
|
||||
|
||||
fooBarBazAddr := f.KeyAddress(keyFooBarBaz)
|
||||
bazAddr := f.KeyAddress(keyBaz)
|
||||
|
||||
// Send some tokens from one account to the other
|
||||
success, _, _ := f.TxSend(keyFoo, fooBarBazAddr, sdk.NewInt64Coin(denom, 10))
|
||||
require.True(t, success)
|
||||
tests.WaitForNextNBlocksTM(1, f.Port)
|
||||
|
||||
// Ensure account balances match expected
|
||||
fooBarBazAcc := f.QueryAccount(fooBarBazAddr)
|
||||
require.Equal(t, int64(10), fooBarBazAcc.GetCoins().AmountOf(denom).Int64())
|
||||
|
||||
// Test generate sendTx with multisig
|
||||
success, stdout, stderr := f.TxSend(keyFooBarBaz, bazAddr, sdk.NewInt64Coin(denom, 10), "--generate-only")
|
||||
require.True(t, success)
|
||||
require.Empty(t, stderr)
|
||||
|
||||
// Write the output to disk
|
||||
unsignedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(unsignedTxFile.Name())
|
||||
|
||||
// Sign with foo's key
|
||||
success, stdout, _ = f.TxSign(keyFoo, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String())
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
fooSignatureFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(fooSignatureFile.Name())
|
||||
|
||||
// Sign with bar's key
|
||||
success, stdout, _ = f.TxSign(keyBar, unsignedTxFile.Name(), "--multisig", fooBarBazAddr.String())
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
barSignatureFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(barSignatureFile.Name())
|
||||
|
||||
// Multisign
|
||||
success, stdout, _ = f.TxMultisign(unsignedTxFile.Name(), keyFooBarBaz, []string{
|
||||
fooSignatureFile.Name(), barSignatureFile.Name()})
|
||||
require.True(t, success)
|
||||
|
||||
// Write the output to disk
|
||||
signedTxFile := writeToNewTempFile(t, stdout)
|
||||
defer os.Remove(signedTxFile.Name())
|
||||
|
||||
// Validate the multisignature
|
||||
success, _, _ = f.TxSign(keyFooBarBaz, signedTxFile.Name(), "--validate-signatures", "--json")
|
||||
require.True(t, success)
|
||||
|
||||
// Broadcast the transaction
|
||||
success, _, _ = f.TxBroadcast(signedTxFile.Name())
|
||||
require.True(t, success)
|
||||
}
|
||||
|
||||
func TestGaiaCLIConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := InitFixtures(t)
|
||||
|
||||
@@ -27,11 +27,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
denom = "stake"
|
||||
keyFoo = "foo"
|
||||
keyBar = "bar"
|
||||
fooDenom = "footoken"
|
||||
feeDenom = "feetoken"
|
||||
denom = "stake"
|
||||
keyFoo = "foo"
|
||||
keyBar = "bar"
|
||||
keyBaz = "baz"
|
||||
keyFooBarBaz = "foobarbaz"
|
||||
fooDenom = "footoken"
|
||||
feeDenom = "feetoken"
|
||||
)
|
||||
|
||||
var startCoins = sdk.Coins{
|
||||
@@ -83,8 +85,13 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
|
||||
// Ensure keystore has foo and bar keys
|
||||
f.KeysDelete(keyFoo)
|
||||
f.KeysDelete(keyBar)
|
||||
f.KeysDelete(keyBar)
|
||||
f.KeysDelete(keyFooBarBaz)
|
||||
f.KeysAdd(keyFoo)
|
||||
f.KeysAdd(keyBar)
|
||||
f.KeysAdd(keyBaz)
|
||||
f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf(
|
||||
"--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz))
|
||||
|
||||
// Ensure that CLI output is in JSON format
|
||||
f.CLIConfig("output", "json")
|
||||
@@ -175,7 +182,7 @@ func (f *Fixtures) GDStart(flags ...string) *tests.Process {
|
||||
// KeysDelete is gaiacli keys delete
|
||||
func (f *Fixtures) KeysDelete(name string, flags ...string) {
|
||||
cmd := fmt.Sprintf("gaiacli keys delete --home=%s %s", f.GCLIHome, name)
|
||||
executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||
executeWrite(f.T, addFlags(cmd, append(append(flags, "-y"), "-f")))
|
||||
}
|
||||
|
||||
// KeysAdd is gaiacli keys add
|
||||
@@ -232,6 +239,16 @@ func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string,
|
||||
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass)
|
||||
}
|
||||
|
||||
// TxMultisign is gaiacli tx multisign
|
||||
func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string,
|
||||
flags ...string) (bool, string, string) {
|
||||
|
||||
cmd := fmt.Sprintf("gaiacli tx multisign %v %s %s %s", f.Flags(),
|
||||
fileName, name, strings.Join(signaturesFiles, " "),
|
||||
)
|
||||
return executeWriteRetStdStreams(f.T, cmd)
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// gaiacli tx staking
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
|
||||
bankcmd.SendTxCmd(cdc),
|
||||
client.LineBreak,
|
||||
authcmd.GetSignCommand(cdc),
|
||||
authcmd.GetMultiSignCommand(cdc),
|
||||
bankcmd.GetBroadcastCommand(cdc),
|
||||
client.LineBreak,
|
||||
)
|
||||
|
||||
@@ -2,10 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
"io"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@@ -2,12 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
|
||||
cpm "github.com/otiai10/copy"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
@@ -19,7 +20,7 @@ import (
|
||||
// AddGenesisAccountCmd returns add-genesis-account cobra Command
|
||||
func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add-genesis-account [address] [coin][,[coin]]",
|
||||
Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
|
||||
Short: "Add genesis account to genesis.json",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
@@ -28,7 +29,15 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
|
||||
|
||||
addr, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
kb, err := keys.GetKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := kb.Get(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr = info.GetAddress()
|
||||
}
|
||||
coins, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
@@ -60,6 +69,7 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command
|
||||
}
|
||||
|
||||
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
||||
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user