From 6e3d8263cc59ba7e391d55575604b43cc42e093b Mon Sep 17 00:00:00 2001 From: Adam Bozanich Date: Fri, 8 May 2020 01:30:55 -0700 Subject: [PATCH] client/keys/parse: honor config changes `keys parse` uses the global configuration before before client applications have had a chance to apply their settings. This change adds a `GetSealedConfig()` helper that waits for the config to be sealed before returning it. Fixes: #5091 Origin: https://github.com/cosmos/cosmos-sdk/commit/4e328d75dbe0d06f1d1a3002d31803ca039afef3 Author: Adam Bozanich Reviewed-by: Alessio Treglia --- client/keys/parse.go | 54 +++++++++++++++++++++++++++------------ client/keys/parse_test.go | 5 +++- types/config.go | 47 ++++++++++++++++++++++++++-------- types/config_test.go | 50 ++++++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 types/config_test.go diff --git a/client/keys/parse.go b/client/keys/parse.go index 79ccca739f0a..858b71db175b 100644 --- a/client/keys/parse.go +++ b/client/keys/parse.go @@ -1,9 +1,11 @@ package keys import ( + "context" "encoding/hex" "errors" "fmt" + "io" "strings" "github.com/spf13/cobra" @@ -17,14 +19,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var config = sdk.GetConfig() -var bech32Prefixes = []string{ - config.GetBech32AccountAddrPrefix(), - config.GetBech32AccountPubPrefix(), - config.GetBech32ValidatorAddrPrefix(), - config.GetBech32ValidatorPubPrefix(), - config.GetBech32ConsensusAddrPrefix(), - config.GetBech32ConsensusPubPrefix(), +func bech32Prefixes(config *sdk.Config) []string { + return []string{ + config.GetBech32AccountAddrPrefix(), + config.GetBech32AccountPubPrefix(), + config.GetBech32ValidatorAddrPrefix(), + config.GetBech32ValidatorPubPrefix(), + config.GetBech32ConsensusAddrPrefix(), + config.GetBech32ConsensusPubPrefix(), + } } type hexOutput struct { @@ -44,13 +47,16 @@ type bech32Output struct { Formats []string `json:"formats"` } -func newBech32Output(bs []byte) bech32Output { +func newBech32Output(config *sdk.Config, bs []byte) bech32Output { + bech32Prefixes := bech32Prefixes(config) out := bech32Output{Formats: make([]string, len(bech32Prefixes))} + for i, prefix := range bech32Prefixes { bech32Addr, err := bech32.ConvertAndEncode(prefix, bs) if err != nil { panic(err) } + out.Formats[i] = bech32Addr } @@ -83,38 +89,52 @@ hexadecimal into bech32 cosmos prefixed format and vice versa. return cmd } -func parseKey(_ *cobra.Command, args []string) error { +func parseKey(cmd *cobra.Command, args []string) error { + config, _ := sdk.GetSealedConfig(context.Background()) + + return doParseKey(cmd, config, args) +} + +func doParseKey(cmd *cobra.Command, config *sdk.Config, args []string) error { addr := strings.TrimSpace(args[0]) + outstream := cmd.OutOrStdout() + if len(addr) == 0 { return errors.New("couldn't parse empty input") } - if !(runFromBech32(addr) || runFromHex(addr)) { + + if !(runFromBech32(outstream, addr) || runFromHex(config, outstream, addr)) { return errors.New("couldn't find valid bech32 nor hex data") } + return nil } // print info from bech32 -func runFromBech32(bech32str string) bool { +func runFromBech32(w io.Writer, bech32str string) bool { hrp, bz, err := bech32.DecodeAndConvert(bech32str) if err != nil { return false } - displayParseKeyInfo(newHexOutput(hrp, bz)) + + displayParseKeyInfo(w, newHexOutput(hrp, bz)) + return true } // print info from hex -func runFromHex(hexstr string) bool { +func runFromHex(config *sdk.Config, w io.Writer, hexstr string) bool { bz, err := hex.DecodeString(hexstr) if err != nil { return false } - displayParseKeyInfo(newBech32Output(bz)) + + displayParseKeyInfo(w, newBech32Output(config, bz)) + return true } -func displayParseKeyInfo(stringer fmt.Stringer) { +func displayParseKeyInfo(w io.Writer, stringer fmt.Stringer) { var out []byte var err error @@ -136,5 +156,5 @@ func displayParseKeyInfo(stringer fmt.Stringer) { panic(err) } - fmt.Println(string(out)) + _, _ = fmt.Fprintln(w, string(out)) } diff --git a/client/keys/parse_test.go b/client/keys/parse_test.go index 759deb386c75..4c226cdff60e 100644 --- a/client/keys/parse_test.go +++ b/client/keys/parse_test.go @@ -3,6 +3,7 @@ package keys import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -10,6 +11,8 @@ func TestParseKey(t *testing.T) { bech32str := "cosmos104ytdpvrx9284zd50v9ep8c6j7pua7dkk0x3ek" hexstr := "EB5AE9872103497EC092EF901027049E4F39200C60040D3562CD7F104A39F62E6E5A39A818F4" + config := sdk.NewConfig() + tests := []struct { name string args []string @@ -23,7 +26,7 @@ func TestParseKey(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.wantErr, parseKey(nil, tt.args) != nil) + require.Equal(t, tt.wantErr, doParseKey(ParseKeyStringCommand(), config, tt.args) != nil) }) } } diff --git a/types/config.go b/types/config.go index 3e39fe30c272..a3181703df1e 100644 --- a/types/config.go +++ b/types/config.go @@ -1,6 +1,7 @@ package types import ( + "context" "sync" "github.com/cosmos/cosmos-sdk/version" @@ -19,19 +20,19 @@ type Config struct { mtx sync.RWMutex coinType uint32 sealed bool + sealedch chan struct{} } // cosmos-sdk wide global singleton -var sdkConfig *Config - -// GetConfig returns the config instance for the SDK. -func GetConfig() *Config { - if sdkConfig != nil { - return sdkConfig - } +var ( + sdkConfig *Config + initConfig sync.Once +) - sdkConfig = &Config{ - sealed: false, +// New returns a new Config with default values. +func NewConfig() *Config { + return &Config{ + sealedch: make(chan struct{}), bech32AddressPrefix: map[string]string{ "account_addr": Bech32PrefixAccAddr, "validator_addr": Bech32PrefixValAddr, @@ -44,9 +45,27 @@ func GetConfig() *Config { fullFundraiserPath: FullFundraiserPath, txEncoder: nil, } +} + +// GetConfig returns the config instance for the SDK. +func GetConfig() *Config { + initConfig.Do(func() { + sdkConfig = NewConfig() + }) return sdkConfig } +// GetSealedConfig returns the config instance for the SDK if/once it is sealed. +func GetSealedConfig(ctx context.Context) (*Config, error) { + config := GetConfig() + select { + case <-config.sealedch: + return config, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + func (config *Config) assertNotSealed() { config.mtx.Lock() defer config.mtx.Unlock() @@ -108,9 +127,17 @@ func (config *Config) SetFullFundraiserPath(fullFundraiserPath string) { // Seal seals the config such that the config state could not be modified further func (config *Config) Seal() *Config { config.mtx.Lock() - defer config.mtx.Unlock() + if config.sealed { + config.mtx.Unlock() + return config + } + + // signal sealed after state exposed/unlocked config.sealed = true + config.mtx.Unlock() + close(config.sealedch) + return config } diff --git a/types/config_test.go b/types/config_test.go new file mode 100644 index 000000000000..df208e32f361 --- /dev/null +++ b/types/config_test.go @@ -0,0 +1,50 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestConfig_SetCoinType(t *testing.T) { + config := sdk.NewConfig() + config.SetCoinType(1) + require.Equal(t, uint32(1), config.GetCoinType()) + config.SetCoinType(99) + require.Equal(t, uint32(99), config.GetCoinType()) + + config.Seal() + require.Panics(t, func() { config.SetCoinType(99) }) +} + +func TestConfig_SetTxEncoder(t *testing.T) { + mockErr := errors.New("test") + config := sdk.NewConfig() + require.Nil(t, config.GetTxEncoder()) + encFunc := sdk.TxEncoder(func(tx sdk.Tx) ([]byte, error) { return nil, nil }) + config.SetTxEncoder(encFunc) + _, err := config.GetTxEncoder()(sdk.Tx(nil)) + require.Error(t, mockErr, err) + + config.Seal() + require.Panics(t, func() { config.SetTxEncoder(encFunc) }) +} + +func TestConfig_SetFullFundraiserPath(t *testing.T) { + config := sdk.NewConfig() + config.SetFullFundraiserPath("test/path") + require.Equal(t, "test/path", config.GetFullFundraiserPath()) + + config.SetFullFundraiserPath("test/poth") + require.Equal(t, "test/poth", config.GetFullFundraiserPath()) + + config.Seal() + require.Panics(t, func() { config.SetFullFundraiserPath("x/test/path") }) +} + +func TestKeyringServiceName(t *testing.T) { + require.Equal(t, sdk.DefaultKeyringServiceName, sdk.KeyringServiceName()) +}