From f84d9fa0785d9e20f88a9eb1891a68b3e9c7d40c Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Wed, 2 Oct 2019 20:29:28 -0400 Subject: [PATCH] Merge PR #5136: Refactor CLIContext to allow multi-chain verifiers --- CHANGELOG.md | 19 ++++++- client/context/context.go | 91 +++++++++------------------------ client/context/verifier.go | 51 ++++++++++++++++++ client/context/verifier_test.go | 35 +++++++++++++ 4 files changed, 129 insertions(+), 67 deletions(-) create mode 100644 client/context/verifier.go create mode 100644 client/context/verifier_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dca759b84..9e3707620e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,26 @@ correct version via: `pkgutil --pkg-info=com.apple.pkg.CLTools_Executables`. * (keys) [\#5097](https://github.com/cosmos/cosmos-sdk/pull/5097) New `keys migrate` command to assist users migrate their keys to the new keyring. - ### Improvements +* (cli) [\#5116](https://github.com/cosmos/cosmos-sdk/issues/5116) The `CLIContext` now supports multiple verifiers +when connecting to multiple chains. The connecting chain's `CLIContext` will have to have the correct +chain ID and node URI or client set. To use a `CLIContext` with a verifier for another chain: + + ```go + // main or parent chain (chain as if you're running without IBC) + mainCtx := context.NewCLIContext() + + // connecting IBC chain + sideCtx := context.NewCLIContext(). + WithChainID(sideChainID). + WithNodeURI(sideChainNodeURI) // or .WithClient(...) + + sideCtx = sideCtx.WithVerifier( + context.CreateVerifier(sideCtx, context.DefaultVerifierCacheSize), + ) + ``` + * (modules) [\#5017](https://github.com/cosmos/cosmos-sdk/pull/5017) The `x/auth` package now supports generalized genesis accounts through the `GenesisAccount` interface. * (modules) [\#4762](https://github.com/cosmos/cosmos-sdk/issues/4762) Deprecate remove and add permissions in ModuleAccount. diff --git a/client/context/context.go b/client/context/context.go index 398b8c9083..c99fb5a32d 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -1,20 +1,16 @@ package context import ( - "bytes" "fmt" "io" "os" - "path/filepath" "github.com/pkg/errors" "github.com/spf13/viper" yaml "gopkg.in/yaml.v2" "github.com/tendermint/tendermint/libs/cli" - "github.com/tendermint/tendermint/libs/log" tmlite "github.com/tendermint/tendermint/lite" - tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -24,27 +20,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var ( - verifier tmlite.Verifier - verifierHome string -) - // CLIContext implements a typical CLI context created in SDK modules for // transaction handling and queries. type CLIContext struct { Codec *codec.Codec Client rpcclient.Client + ChainID string Keybase cryptokeys.Keybase Output io.Writer OutputFormat string Height int64 + HomeDir string NodeURI string From string TrustNode bool UseLedger bool BroadcastMode string Verifier tmlite.Verifier - VerifierHome string Simulate bool GenerateOnly bool FromAddress sdk.AccAddress @@ -55,7 +47,10 @@ type CLIContext struct { // NewCLIContextWithFrom returns a new initialized CLIContext with parameters from the // command line using Viper. It takes a key name or address and populates the FromName and -// FromAddress field accordingly. +// FromAddress field accordingly. It will also create Tendermint verifier using +// the chain ID, home directory and RPC URI provided by the command line. If using +// a CLIContext in tests or any non CLI-based environment, the verifier will not +// be created and will be set as nil because FlagTrustNode must be set. func NewCLIContextWithFrom(from string) CLIContext { var nodeURI string var rpc rpcclient.Client @@ -74,23 +69,18 @@ func NewCLIContextWithFrom(from string) CLIContext { } } - // We need to use a single verifier for all contexts - if verifier == nil || verifierHome != viper.GetString(flags.FlagHome) { - verifier = createVerifier() - verifierHome = viper.GetString(flags.FlagHome) - } - - return CLIContext{ + ctx := CLIContext{ Client: rpc, + ChainID: viper.GetString(flags.FlagChainID), Output: os.Stdout, NodeURI: nodeURI, From: viper.GetString(flags.FlagFrom), OutputFormat: viper.GetString(cli.OutputFlag), Height: viper.GetInt64(flags.FlagHeight), + HomeDir: viper.GetString(flags.FlagHome), TrustNode: viper.GetBool(flags.FlagTrustNode), UseLedger: viper.GetBool(flags.FlagUseLedger), BroadcastMode: viper.GetString(flags.FlagBroadcastMode), - Verifier: verifier, Simulate: viper.GetBool(flags.FlagDryRun), GenerateOnly: genOnly, FromAddress: fromAddress, @@ -98,58 +88,21 @@ func NewCLIContextWithFrom(from string) CLIContext { Indent: viper.GetBool(flags.FlagIndentResponse), SkipConfirm: viper.GetBool(flags.FlagSkipConfirmation), } + + // create a verifier for the specific chain ID and RPC client + verifier, err := CreateVerifier(ctx, DefaultVerifierCacheSize) + if err != nil && viper.IsSet(flags.FlagTrustNode) { + fmt.Printf("failed to create verifier: %s\n", err) + os.Exit(1) + } + + return ctx.WithVerifier(verifier) } // NewCLIContext returns a new initialized CLIContext with parameters from the // command line using Viper. func NewCLIContext() CLIContext { return NewCLIContextWithFrom(viper.GetString(flags.FlagFrom)) } -func createVerifier() tmlite.Verifier { - trustNodeDefined := viper.IsSet(flags.FlagTrustNode) - if !trustNodeDefined { - return nil - } - - trustNode := viper.GetBool(flags.FlagTrustNode) - if trustNode { - return nil - } - - chainID := viper.GetString(flags.FlagChainID) - home := viper.GetString(flags.FlagHome) - nodeURI := viper.GetString(flags.FlagNode) - - var errMsg bytes.Buffer - if chainID == "" { - errMsg.WriteString("--chain-id ") - } - if home == "" { - errMsg.WriteString("--home ") - } - if nodeURI == "" { - errMsg.WriteString("--node ") - } - if errMsg.Len() != 0 { - fmt.Printf("Must specify these options: %s when --trust-node is false\n", errMsg.String()) - os.Exit(1) - } - - node := rpcclient.NewHTTP(nodeURI, "/websocket") - cacheSize := 10 // TODO: determine appropriate cache size - verifier, err := tmliteProxy.NewVerifier( - chainID, filepath.Join(home, ".lite_verifier"), - node, log.NewNopLogger(), cacheSize, - ) - - if err != nil { - fmt.Printf("Create verifier failed: %s\n", err.Error()) - fmt.Printf("Please check network connection and verify the address of the node to connect to\n") - os.Exit(1) - } - - return verifier -} - // WithCodec returns a copy of the context with an updated codec. func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext { ctx.Codec = cdc @@ -200,12 +153,18 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { return ctx } -// WithVerifier - return a copy of the context with an updated Verifier +// WithVerifier returns a copy of the context with an updated Verifier. func (ctx CLIContext) WithVerifier(verifier tmlite.Verifier) CLIContext { ctx.Verifier = verifier return ctx } +// WithChainID returns a copy of the context with an updated chain ID. +func (ctx CLIContext) WithChainID(chainID string) CLIContext { + ctx.ChainID = chainID + return ctx +} + // WithGenerateOnly returns a copy of the context with updated GenerateOnly value func (ctx CLIContext) WithGenerateOnly(generateOnly bool) CLIContext { ctx.GenerateOnly = generateOnly diff --git a/client/context/verifier.go b/client/context/verifier.go new file mode 100644 index 0000000000..73a816745b --- /dev/null +++ b/client/context/verifier.go @@ -0,0 +1,51 @@ +package context + +import ( + "path/filepath" + + "github.com/pkg/errors" + "github.com/tendermint/tendermint/libs/log" + tmlite "github.com/tendermint/tendermint/lite" + tmliteproxy "github.com/tendermint/tendermint/lite/proxy" + rpcclient "github.com/tendermint/tendermint/rpc/client" +) + +const ( + verifierDir = ".lite_verifier" + + // DefaultVerifierCacheSize defines the default Tendermint cache size. + DefaultVerifierCacheSize = 10 +) + +// CreateVerifier returns a Tendermint verifier from a CLIContext object and +// cache size. An error is returned if the CLIContext is missing required values +// or if the verifier could not be created. A CLIContext must at the very least +// have the chain ID and home directory set. If the CLIContext has TrustNode +// enabled, no verifier will be created. +func CreateVerifier(ctx CLIContext, cacheSize int) (tmlite.Verifier, error) { + if ctx.TrustNode { + return nil, nil + } + + switch { + case ctx.ChainID == "": + return nil, errors.New("must provide a valid chain ID to create verifier") + + case ctx.HomeDir == "": + return nil, errors.New("must provide a valid home directory to create verifier") + + case ctx.Client == nil && ctx.NodeURI == "": + return nil, errors.New("must provide a valid RPC client or RPC URI to create verifier") + } + + // create an RPC client based off of the RPC URI if no RPC client exists + client := ctx.Client + if client == nil { + client = rpcclient.NewHTTP(ctx.NodeURI, "/websocket") + } + + return tmliteproxy.NewVerifier( + ctx.ChainID, filepath.Join(ctx.HomeDir, ctx.ChainID, verifierDir), + client, log.NewNopLogger(), cacheSize, + ) +} diff --git a/client/context/verifier_test.go b/client/context/verifier_test.go new file mode 100644 index 0000000000..d0785c8125 --- /dev/null +++ b/client/context/verifier_test.go @@ -0,0 +1,35 @@ +package context_test + +import ( + "io/ioutil" + "testing" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/stretchr/testify/require" +) + +func TestCreateVerifier(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "example") + require.NoError(t, err) + + testCases := []struct { + name string + ctx context.CLIContext + expectErr bool + }{ + {"no chain ID", context.CLIContext{}, true}, + {"no home directory", context.CLIContext{}.WithChainID("test"), true}, + {"no client or RPC URI", context.CLIContext{HomeDir: tmpDir}.WithChainID("test"), true}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + verifier, err := context.CreateVerifier(tc.ctx, context.DefaultVerifierCacheSize) + require.Equal(t, tc.expectErr, err != nil, err) + + if !tc.expectErr { + require.NotNil(t, verifier) + } + }) + } +}