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
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.

View File

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

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