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