Merge PR #5136: Refactor CLIContext to allow multi-chain verifiers

This commit is contained in:
Alexander Bezobchuk 2019-10-02 20:29:28 -04:00 committed by GitHub
parent 532ea18724
commit f84d9fa078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 67 deletions

View File

@ -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 * (keys) [\#5097](https://github.com/cosmos/cosmos-sdk/pull/5097) New `keys migrate` command to assist users migrate their keys
to the new keyring. to the new keyring.
### Improvements ### 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 * (modules) [\#5017](https://github.com/cosmos/cosmos-sdk/pull/5017) The `x/auth` package now supports
generalized genesis accounts through the `GenesisAccount` interface. generalized genesis accounts through the `GenesisAccount` interface.
* (modules) [\#4762](https://github.com/cosmos/cosmos-sdk/issues/4762) Deprecate remove and add permissions in ModuleAccount. * (modules) [\#4762](https://github.com/cosmos/cosmos-sdk/issues/4762) Deprecate remove and add permissions in ModuleAccount.

View File

@ -1,20 +1,16 @@
package context package context
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/viper" "github.com/spf13/viper"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
tmlite "github.com/tendermint/tendermint/lite" tmlite "github.com/tendermint/tendermint/lite"
tmliteProxy "github.com/tendermint/tendermint/lite/proxy"
rpcclient "github.com/tendermint/tendermint/rpc/client" rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
@ -24,27 +20,23 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
var (
verifier tmlite.Verifier
verifierHome string
)
// CLIContext implements a typical CLI context created in SDK modules for // CLIContext implements a typical CLI context created in SDK modules for
// transaction handling and queries. // transaction handling and queries.
type CLIContext struct { type CLIContext struct {
Codec *codec.Codec Codec *codec.Codec
Client rpcclient.Client Client rpcclient.Client
ChainID string
Keybase cryptokeys.Keybase Keybase cryptokeys.Keybase
Output io.Writer Output io.Writer
OutputFormat string OutputFormat string
Height int64 Height int64
HomeDir string
NodeURI string NodeURI string
From string From string
TrustNode bool TrustNode bool
UseLedger bool UseLedger bool
BroadcastMode string BroadcastMode string
Verifier tmlite.Verifier Verifier tmlite.Verifier
VerifierHome string
Simulate bool Simulate bool
GenerateOnly bool GenerateOnly bool
FromAddress sdk.AccAddress FromAddress sdk.AccAddress
@ -55,7 +47,10 @@ type CLIContext struct {
// NewCLIContextWithFrom returns a new initialized CLIContext with parameters from the // 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 // 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 { func NewCLIContextWithFrom(from string) CLIContext {
var nodeURI string var nodeURI string
var rpc rpcclient.Client var rpc rpcclient.Client
@ -74,23 +69,18 @@ func NewCLIContextWithFrom(from string) CLIContext {
} }
} }
// We need to use a single verifier for all contexts ctx := CLIContext{
if verifier == nil || verifierHome != viper.GetString(flags.FlagHome) {
verifier = createVerifier()
verifierHome = viper.GetString(flags.FlagHome)
}
return CLIContext{
Client: rpc, Client: rpc,
ChainID: viper.GetString(flags.FlagChainID),
Output: os.Stdout, Output: os.Stdout,
NodeURI: nodeURI, NodeURI: nodeURI,
From: viper.GetString(flags.FlagFrom), From: viper.GetString(flags.FlagFrom),
OutputFormat: viper.GetString(cli.OutputFlag), OutputFormat: viper.GetString(cli.OutputFlag),
Height: viper.GetInt64(flags.FlagHeight), Height: viper.GetInt64(flags.FlagHeight),
HomeDir: viper.GetString(flags.FlagHome),
TrustNode: viper.GetBool(flags.FlagTrustNode), TrustNode: viper.GetBool(flags.FlagTrustNode),
UseLedger: viper.GetBool(flags.FlagUseLedger), UseLedger: viper.GetBool(flags.FlagUseLedger),
BroadcastMode: viper.GetString(flags.FlagBroadcastMode), BroadcastMode: viper.GetString(flags.FlagBroadcastMode),
Verifier: verifier,
Simulate: viper.GetBool(flags.FlagDryRun), Simulate: viper.GetBool(flags.FlagDryRun),
GenerateOnly: genOnly, GenerateOnly: genOnly,
FromAddress: fromAddress, FromAddress: fromAddress,
@ -98,58 +88,21 @@ func NewCLIContextWithFrom(from string) CLIContext {
Indent: viper.GetBool(flags.FlagIndentResponse), Indent: viper.GetBool(flags.FlagIndentResponse),
SkipConfirm: viper.GetBool(flags.FlagSkipConfirmation), 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 // NewCLIContext returns a new initialized CLIContext with parameters from the
// command line using Viper. // command line using Viper.
func NewCLIContext() CLIContext { return NewCLIContextWithFrom(viper.GetString(flags.FlagFrom)) } 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. // WithCodec returns a copy of the context with an updated codec.
func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext { func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext {
ctx.Codec = cdc ctx.Codec = cdc
@ -200,12 +153,18 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext {
return ctx 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 { func (ctx CLIContext) WithVerifier(verifier tmlite.Verifier) CLIContext {
ctx.Verifier = verifier ctx.Verifier = verifier
return ctx 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 // WithGenerateOnly returns a copy of the context with updated GenerateOnly value
func (ctx CLIContext) WithGenerateOnly(generateOnly bool) CLIContext { func (ctx CLIContext) WithGenerateOnly(generateOnly bool) CLIContext {
ctx.GenerateOnly = generateOnly ctx.GenerateOnly = generateOnly

View File

@ -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,
)
}

View File

@ -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)
}
})
}
}