Skip to content

Commit

Permalink
client/keys/parse: honor config changes (#6340)
Browse files Browse the repository at this point in the history
`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: 4e328d7
Author: Adam Bozanich <adam.boz@gmail.com>
Reviewed-by: Alessio Treglia <alessio@tendermint.com>
  • Loading branch information
Alessio Treglia committed Jun 9, 2020
1 parent 1c917e2 commit dd6f8c8
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 28 deletions.
54 changes: 37 additions & 17 deletions client/keys/parse.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package keys

import (
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"

"github.com/spf13/cobra"
Expand All @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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

Expand All @@ -136,5 +156,5 @@ func displayParseKeyInfo(stringer fmt.Stringer) {
panic(err)
}

fmt.Println(string(out))
_, _ = fmt.Fprintln(w, string(out))
}
5 changes: 4 additions & 1 deletion client/keys/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package keys
import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func TestParseKey(t *testing.T) {
bech32str := "cosmos104ytdpvrx9284zd50v9ep8c6j7pua7dkk0x3ek"
hexstr := "EB5AE9872103497EC092EF901027049E4F39200C60040D3562CD7F104A39F62E6E5A39A818F4"

config := sdk.NewConfig()

tests := []struct {
name string
args []string
Expand All @@ -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)
})
}
}
47 changes: 37 additions & 10 deletions types/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"context"
"sync"

"github.com/cosmos/cosmos-sdk/version"
Expand All @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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
}

Expand Down
50 changes: 50 additions & 0 deletions types/config_test.go
Original file line number Diff line number Diff line change
@@ -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())
}

0 comments on commit dd6f8c8

Please sign in to comment.