Merge PR #5136: Refactor CLIContext to allow multi-chain verifiers
This commit is contained in:
parent
532ea18724
commit
f84d9fa078
19
CHANGELOG.md
19
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
|
* (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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue