Merge PR #3748: Multisig Display UX Improvements

This commit is contained in:
Alexander Bezobchuk
2019-03-01 13:29:33 -08:00
committed by Jack Zampolin
parent c2aecb8b0e
commit 47a44fb580
20 changed files with 387 additions and 187 deletions
+3 -3
View File
@@ -142,7 +142,7 @@ func runAddCmd(_ *cobra.Command, args []string) error {
}
pk := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks)
if _, err := kb.CreateOffline(name, pk); err != nil {
if _, err := kb.CreateMulti(name, pk); err != nil {
return err
}
@@ -263,7 +263,7 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
switch output {
case OutputFormatText:
fmt.Fprintln(os.Stderr)
printKeyInfo(info, Bech32KeyOutput)
printKeyInfo(info, keys.Bech32KeyOutput)
// print mnemonic unless requested not to.
if showMnemonic {
@@ -273,7 +273,7 @@ func printCreate(info keys.Info, showMnemonic bool, mnemonic string) error {
fmt.Fprintln(os.Stderr, mnemonic)
}
case OutputFormatJSON:
out, err := Bech32KeyOutput(info)
out, err := keys.Bech32KeyOutput(info)
if err != nil {
return err
}
+15 -13
View File
@@ -6,35 +6,37 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/keys"
)
type testCases struct {
Keys []KeyOutput
Answers []KeyOutput
Keys []keys.KeyOutput
Answers []keys.KeyOutput
JSON [][]byte
}
func getTestCases() testCases {
return testCases{
[]KeyOutput{
{"A", "B", "C", "D", "E"},
{"A", "B", "C", "D", ""},
{"", "B", "C", "D", ""},
{"", "", "", "", ""},
[]keys.KeyOutput{
{"A", "B", "C", "D", "E", 0, nil},
{"A", "B", "C", "D", "", 0, nil},
{"", "B", "C", "D", "", 0, nil},
{"", "", "", "", "", 0, nil},
},
make([]KeyOutput, 4),
make([]keys.KeyOutput, 4),
[][]byte{
[]byte(`{"name":"A","type":"B","address":"C","pub_key":"D","mnemonic":"E"}`),
[]byte(`{"name":"A","type":"B","address":"C","pub_key":"D"}`),
[]byte(`{"name":"","type":"B","address":"C","pub_key":"D"}`),
[]byte(`{"name":"","type":"","address":"","pub_key":""}`),
[]byte(`{"name":"A","type":"B","address":"C","pubkey":"D","mnemonic":"E"}`),
[]byte(`{"name":"A","type":"B","address":"C","pubkey":"D"}`),
[]byte(`{"name":"","type":"B","address":"C","pubkey":"D"}`),
[]byte(`{"name":"","type":"","address":"","pubkey":""}`),
},
}
}
func TestMarshalJSON(t *testing.T) {
type args struct {
o KeyOutput
o keys.KeyOutput
}
data := getTestCases()
+22 -31
View File
@@ -6,7 +6,6 @@ import (
"github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/cobra"
@@ -27,39 +26,29 @@ const (
// FlagBechPrefix defines a desired Bech32 prefix encoding for a key.
FlagDevice = "device"
flagMultiSigThreshold = "multisig-threshold"
flagMultiSigThreshold = "multisig-threshold"
flagShowMultiSig = "show-multisig"
defaultMultiSigKeyName = "multi"
)
var _ keys.Info = (*multiSigKey)(nil)
type multiSigKey struct {
name string
key tmcrypto.PubKey
}
func (m multiSigKey) GetName() string { return m.name }
func (m multiSigKey) GetType() keys.KeyType { return keys.TypeLocal }
func (m multiSigKey) GetPubKey() tmcrypto.PubKey { return m.key }
func (m multiSigKey) GetAddress() sdk.AccAddress { return sdk.AccAddress(m.key.Address()) }
func (m multiSigKey) GetPath() (*hd.BIP44Params, error) {
return nil, fmt.Errorf("BIP44 Paths are not available for this type")
}
func showKeysCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "show [name]",
Use: "show [name [name...]]",
Short: "Show key info for the given name",
Long: `Return public details of one local key.`,
Args: cobra.MinimumNArgs(1),
RunE: runShowCmd,
Long: `Return public details of a single local key. If multiple names are
provided, then an ephemeral multisig key will be created under the name "multi"
consisting of all the keys provided by name and multisig threshold.`,
Args: cobra.MinimumNArgs(1),
RunE: runShowCmd,
}
cmd.Flags().String(FlagBechPrefix, sdk.PrefixAccount, "The Bech32 prefix encoding for a key (acc|val|cons)")
cmd.Flags().BoolP(FlagAddress, "a", false, "output the address only (overrides --output)")
cmd.Flags().BoolP(FlagPublicKey, "p", false, "output the public key only (overrides --output)")
cmd.Flags().BoolP(FlagDevice, "d", false, "output the address in the device")
cmd.Flags().BoolP(FlagAddress, "a", false, "Output the address only (overrides --output)")
cmd.Flags().BoolP(FlagPublicKey, "p", false, "Output the public key only (overrides --output)")
cmd.Flags().BoolP(FlagDevice, "d", false, "Output the address in the device")
cmd.Flags().Uint(flagMultiSigThreshold, 1, "K out of N required signatures")
cmd.Flags().BoolP(flagShowMultiSig, "m", false, "Output multisig pubkey constituents, threshold, and weights")
return cmd
}
@@ -79,6 +68,7 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
if err != nil {
return err
}
pks[i] = info.GetPubKey()
}
@@ -87,16 +77,15 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
if err != nil {
return err
}
multikey := multisig.NewPubKeyMultisigThreshold(multisigThreshold, pks)
info = multiSigKey{
name: defaultMultiSigKeyName,
key: multikey,
}
info = keys.NewMultiInfo(defaultMultiSigKeyName, multikey)
}
isShowAddr := viper.GetBool(FlagAddress)
isShowPubKey := viper.GetBool(FlagPublicKey)
isShowDevice := viper.GetBool(FlagDevice)
isShowMultiSig := viper.GetBool(flagShowMultiSig)
isOutputSet := false
tmp := cmd.Flag(cli.OutputFlag)
@@ -122,6 +111,8 @@ func runShowCmd(cmd *cobra.Command, args []string) (err error) {
printKeyAddress(info, bechKeyOut)
case isShowPubKey:
printPubKey(info, bechKeyOut)
case isShowMultiSig:
printMultiSigKeyInfo(info, bechKeyOut)
default:
printKeyInfo(info, bechKeyOut)
}
@@ -163,11 +154,11 @@ func validateMultisigThreshold(k, nKeys int) error {
func getBechKeyOut(bechPrefix string) (bechKeyOutFn, error) {
switch bechPrefix {
case sdk.PrefixAccount:
return Bech32KeyOutput, nil
return keys.Bech32KeyOutput, nil
case sdk.PrefixValidator:
return Bech32ValKeyOutput, nil
return keys.Bech32ValKeyOutput, nil
case sdk.PrefixConsensus:
return Bech32ConsKeyOutput, nil
return keys.Bech32ConsKeyOutput, nil
}
return nil, fmt.Errorf("invalid Bech32 prefix encoding provided: %s", bechPrefix)
+10 -11
View File
@@ -10,22 +10,21 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/libs/cli"
)
func Test_multiSigKey_Properties(t *testing.T) {
tmpKey1 := secp256k1.GenPrivKeySecp256k1([]byte("mySecret"))
tmp := multiSigKey{
name: "myMultisig",
key: tmpKey1.PubKey(),
}
pk := multisig.NewPubKeyMultisigThreshold(1, []crypto.PubKey{tmpKey1.PubKey()})
tmp := keys.NewMultiInfo("myMultisig", pk)
assert.Equal(t, "myMultisig", tmp.GetName())
assert.Equal(t, keys.TypeLocal, tmp.GetType())
assert.Equal(t, "015ABFFB09DB738A45745A91E8C401423ECE4016", tmp.GetPubKey().Address().String())
assert.Equal(t, "cosmos1q9dtl7cfmdec53t5t2g733qpgglvusqk6xdntl", tmp.GetAddress().String())
assert.Equal(t, keys.TypeMulti, tmp.GetType())
assert.Equal(t, "79BF2B5B418A85329EC2149D1854D443F56F5A9F", tmp.GetPubKey().Address().String())
assert.Equal(t, "cosmos10xljkk6p32zn98kzzjw3s4x5g06k7k5lz6flcv", tmp.GetAddress().String())
}
func Test_showKeysCmd(t *testing.T) {
@@ -134,9 +133,9 @@ func Test_getBechKeyOut(t *testing.T) {
}{
{"empty", args{""}, nil, true},
{"wrong", args{"???"}, nil, true},
{"acc", args{sdk.PrefixAccount}, Bech32KeyOutput, false},
{"val", args{sdk.PrefixValidator}, Bech32ValKeyOutput, false},
{"cons", args{sdk.PrefixConsensus}, Bech32ConsKeyOutput, false},
{"acc", args{sdk.PrefixAccount}, keys.Bech32KeyOutput, false},
{"val", args{sdk.PrefixValidator}, keys.Bech32ValKeyOutput, false},
{"cons", args{sdk.PrefixConsensus}, keys.Bech32ConsKeyOutput, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
-7
View File
@@ -1,13 +1,6 @@
package keys
// used for outputting keys.Info over REST
type KeyOutput struct {
Name string `json:"name"`
Type string `json:"type"`
Address string `json:"address"`
PubKey string `json:"pub_key"`
Mnemonic string `json:"mnemonic,omitempty"`
}
// AddNewKey request a new key
type AddNewKey struct {
+24 -61
View File
@@ -9,7 +9,6 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// available output formats.
@@ -21,7 +20,7 @@ const (
defaultKeyDBName = "keys"
)
type bechKeyOutFn func(keyInfo keys.Info) (KeyOutput, error)
type bechKeyOutFn func(keyInfo keys.Info) (keys.KeyOutput, error)
// GetKeyInfo returns key info for a given name. An error is returned if the
// keybase cannot be retrieved or getting the info fails.
@@ -90,68 +89,22 @@ func getLazyKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
return keys.New(defaultKeyDBName, filepath.Join(rootDir, "keys")), nil
}
// create a list of KeyOutput in bech32 format
func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) {
kos := make([]KeyOutput, len(infos))
for i, info := range infos {
ko, err := Bech32KeyOutput(info)
if err != nil {
return nil, err
}
kos[i] = ko
}
return kos, nil
func printKeyTextHeader() {
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\tPUBKEY:\n")
}
// create a KeyOutput in bech32 format
func Bech32KeyOutput(info keys.Info) (KeyOutput, error) {
accAddr := sdk.AccAddress(info.GetPubKey().Address().Bytes())
bechPubKey, err := sdk.Bech32ifyAccPub(info.GetPubKey())
if err != nil {
return KeyOutput{}, err
}
return KeyOutput{
Name: info.GetName(),
Type: info.GetType().String(),
Address: accAddr.String(),
PubKey: bechPubKey,
}, nil
func printMultiSigKeyTextHeader() {
fmt.Printf("WEIGHT:\tTHRESHOLD:\tADDRESS:\t\t\t\t\tPUBKEY:\n")
}
// Bech32ConsKeyOutput returns key output for a consensus node's key
// information.
func Bech32ConsKeyOutput(keyInfo keys.Info) (KeyOutput, error) {
consAddr := sdk.ConsAddress(keyInfo.GetPubKey().Address().Bytes())
bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey())
func printMultiSigKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
ko, err := bechKeyOut(keyInfo)
if err != nil {
return KeyOutput{}, err
panic(err)
}
return KeyOutput{
Name: keyInfo.GetName(),
Type: keyInfo.GetType().String(),
Address: consAddr.String(),
PubKey: bechPubKey,
}, nil
}
// Bech32ValKeyOutput returns key output for a validator's key information.
func Bech32ValKeyOutput(keyInfo keys.Info) (KeyOutput, error) {
valAddr := sdk.ValAddress(keyInfo.GetPubKey().Address().Bytes())
bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey())
if err != nil {
return KeyOutput{}, err
}
return KeyOutput{
Name: keyInfo.GetName(),
Type: keyInfo.GetType().String(),
Address: valAddr.String(),
PubKey: bechPubKey,
}, nil
printMultiSigKeyTextHeader()
printMultiSigKeyOutput(ko)
}
func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
@@ -162,8 +115,9 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
switch viper.Get(cli.OutputFlag) {
case OutputFormatText:
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
printKeyTextHeader()
printKeyOutput(ko)
case "json":
out, err := MarshalJSON(ko)
if err != nil {
@@ -175,29 +129,38 @@ func printKeyInfo(keyInfo keys.Info, bechKeyOut bechKeyOutFn) {
}
func printInfos(infos []keys.Info) {
kos, err := Bech32KeysOutput(infos)
kos, err := keys.Bech32KeysOutput(infos)
if err != nil {
panic(err)
}
switch viper.Get(cli.OutputFlag) {
case OutputFormatText:
fmt.Printf("NAME:\tTYPE:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n")
printKeyTextHeader()
for _, ko := range kos {
printKeyOutput(ko)
}
case OutputFormatJSON:
out, err := MarshalJSON(kos)
if err != nil {
panic(err)
}
fmt.Println(string(out))
}
}
func printKeyOutput(ko KeyOutput) {
func printKeyOutput(ko keys.KeyOutput) {
fmt.Printf("%s\t%s\t%s\t%s\n", ko.Name, ko.Type, ko.Address, ko.PubKey)
}
func printMultiSigKeyOutput(ko keys.KeyOutput) {
for _, pk := range ko.PubKeys {
fmt.Printf("%d\t%d\t\t%s\t%s\n", pk.Weight, ko.Threshold, pk.Address, pk.PubKey)
}
}
func printKeyAddress(info keys.Info, bechKeyOut bechKeyOutFn) {
ko, err := bechKeyOut(info)
if err != nil {
+6 -5
View File
@@ -19,8 +19,9 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
@@ -558,7 +559,7 @@ func getKeys(t *testing.T, port string) []keys.KeyOutput {
// POST /keys Create a new account locally
func doKeysPost(t *testing.T, port, name, password, mnemonic string, account int, index int) keys.KeyOutput {
pk := keys.AddNewKey{name, password, mnemonic, account, index}
pk := clientkeys.AddNewKey{name, password, mnemonic, account, index}
req, err := cdc.MarshalJSON(pk)
require.NoError(t, err)
@@ -584,7 +585,7 @@ func getKeysSeed(t *testing.T, port string) string {
// POST /keys/{name}/recove Recover a account from a seed
func doRecoverKey(t *testing.T, port, recoverName, recoverPassword, mnemonic string, account uint32, index uint32) {
pk := keys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
pk := clientkeys.RecoverKey{recoverPassword, mnemonic, int(account), int(index)}
req, err := cdc.MarshalJSON(pk)
require.NoError(t, err)
@@ -612,7 +613,7 @@ func getKey(t *testing.T, port, name string) keys.KeyOutput {
// PUT /keys/{name} Update the password for this account in the KMS
func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail bool) {
kr := keys.UpdateKeyReq{oldPassword, newPassword}
kr := clientkeys.UpdateKeyReq{oldPassword, newPassword}
req, err := cdc.MarshalJSON(kr)
require.NoError(t, err)
keyEndpoint := fmt.Sprintf("/keys/%s", name)
@@ -626,7 +627,7 @@ func updateKey(t *testing.T, port, name, oldPassword, newPassword string, fail b
// DELETE /keys/{name} Remove an account
func deleteKey(t *testing.T, port, name, password string) {
dk := keys.DeleteKeyReq{password}
dk := clientkeys.DeleteKeyReq{password}
req, err := cdc.MarshalJSON(dk)
require.NoError(t, err)
keyEndpoint := fmt.Sprintf("/keys/%s", name)