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:
Alessio Treglia
2019-01-16 09:30:57 -08:00
committed by Jack Zampolin
parent eff1f7ca10
commit 26cb0a125a
18 changed files with 728 additions and 51 deletions
+194
View File
@@ -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)
+23 -6
View File
@@ -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
+1
View File
@@ -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,
)
+1 -1
View File
@@ -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 -1
View File
@@ -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"
+12 -2
View File
@@ -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
}