Implementing --offline flag (#5810)

Closes: #5448
This commit is contained in:
Jonathan Gimeno 2020-03-19 02:49:33 +01:00 committed by GitHub
parent b47e4096fc
commit 8c7bb89549
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 141 additions and 22 deletions

View File

@ -169,6 +169,10 @@ Buffers for state serialization instead of Amino.
* (server) [\#5709](https://github.com/cosmos/cosmos-sdk/pull/5709) There are two new flags for pruning, `--pruning-keep-every`
and `--pruning-snapshot-every` as an alternative to `--pruning`. They allow to fine tune the strategy for pruning the state.
* (crypto/keys) [\#5739](https://github.com/cosmos/cosmos-sdk/pull/5739) Print an error message if the password input failed.
* (client) [\#5810](https://github.com/cosmos/cosmos-sdk/pull/5810) Added a new `--offline` flag that allows commands to
be executed without an internet connection. Previously, `--generate-only` served this purpose in addition to only allowing
txs to be generated. Now, `--generate-only` solely allows txs to be generated without being broadcasted and disallows
Keybase use and `--offline` allows the use of Keybase but does not allow any functionality that requires an online connection.
## [v0.38.1] - 2020-02-11

View File

@ -43,6 +43,7 @@ type CLIContext struct {
UseLedger bool
Simulate bool
GenerateOnly bool
Offline bool
Indent bool
SkipConfirm bool
@ -67,7 +68,8 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
os.Exit(1)
}
if !genOnly {
offline := viper.GetBool(flags.FlagOffline)
if !offline {
nodeURI = viper.GetString(flags.FlagNode)
if nodeURI != "" {
rpc, err = rpcclient.NewHTTP(nodeURI, "/websocket")
@ -93,6 +95,7 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
BroadcastMode: viper.GetString(flags.FlagBroadcastMode),
Simulate: viper.GetBool(flags.FlagDryRun),
GenerateOnly: genOnly,
Offline: offline,
FromAddress: fromAddress,
FromName: fromName,
Indent: viper.GetBool(flags.FlagIndentResponse),
@ -286,7 +289,7 @@ func GetFromFields(input io.Reader, from string, genOnly bool) (sdk.AccAddress,
if genOnly {
addr, err := sdk.AccAddressFromBech32(from)
if err != nil {
return nil, "", errors.Wrap(err, "must provide a valid Bech32 address for generate-only")
return nil, "", errors.Wrap(err, "must provide a valid Bech32 address in generate-only mode")
}
return addr, "", nil

View File

@ -0,0 +1,68 @@
package context
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/client/flags"
)
func TestCLIContext_WithOffline(t *testing.T) {
viper.Set(flags.FlagOffline, true)
viper.Set(flags.FlagNode, "tcp://localhost:26657")
ctx := NewCLIContext()
require.True(t, ctx.Offline)
require.Nil(t, ctx.Client)
viper.Reset()
viper.Set(flags.FlagOffline, false)
viper.Set(flags.FlagNode, "tcp://localhost:26657")
ctx = NewCLIContext()
require.False(t, ctx.Offline)
require.NotNil(t, ctx.Client)
}
func TestCLIContext_WithGenOnly(t *testing.T) {
viper.Set(flags.FlagGenerateOnly, true)
validFromAddr := "cosmos1q7380u26f7ntke3facjmynajs4umlr329vr4ja"
fromAddr, err := sdk.AccAddressFromBech32(validFromAddr)
require.NoError(t, err)
tests := []struct {
name string
from string
expectedFromAddr sdk.AccAddress
expectedFromName string
}{
{
name: "valid from",
from: validFromAddr,
expectedFromAddr: fromAddr,
expectedFromName: "",
},
{
name: "empty from",
from: "",
expectedFromAddr: nil,
expectedFromName: "",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
ctx := NewCLIContextWithFrom(tt.from)
require.Equal(t, tt.expectedFromAddr, ctx.FromAddress)
require.Equal(t, tt.expectedFromName, ctx.FromName)
})
}
}

View File

@ -22,7 +22,7 @@ import (
// error is returned.
func (ctx CLIContext) GetNode() (rpcclient.Client, error) {
if ctx.Client == nil {
return nil, errors.New("no RPC client defined")
return nil, errors.New("no RPC client is defined in offline mode")
}
return ctx.Client, nil

View File

@ -57,6 +57,7 @@ const (
FlagBroadcastMode = "broadcast-mode"
FlagDryRun = "dry-run"
FlagGenerateOnly = "generate-only"
FlagOffline = "offline"
FlagIndentResponse = "indent"
FlagListenAddr = "laddr"
FlagMaxOpenConnections = "max-open"
@ -114,7 +115,8 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().StringP(FlagBroadcastMode, "b", BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)")
c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it")
c.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase is not accessible and the node operates offline)")
c.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase is not accessible)")
c.Flags().Bool(FlagOffline, false, "Offline mode (does not allow any online functionality")
c.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation")
c.Flags().String(FlagKeyringBackend, DefaultKeyringBackend, "Select keyring's backend (os|file|test)")

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"strings"
"github.com/spf13/cobra"
@ -26,6 +27,11 @@ $ <appcli> tx broadcast ./mytxn.json
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
cliCtx := context.NewCLIContext().WithCodec(cdc)
if cliCtx.Offline {
return errors.New("cannot broadcast tx during offline mode")
}
stdTx, err := client.ReadStdTxFromFile(cliCtx.Codec, args[0])
if err != nil {
return

View File

@ -0,0 +1,45 @@
package cli
import (
"io/ioutil"
"path/filepath"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/tests"
)
func TestGetBroadcastCommand_OfflineFlag(t *testing.T) {
codec := amino.NewCodec()
cmd := GetBroadcastCommand(codec)
viper.Set(flags.FlagOffline, true)
err := cmd.RunE(nil, []string{})
require.EqualError(t, err, "cannot broadcast tx during offline mode")
}
func TestGetBroadcastCommand_WithoutOfflineFlag(t *testing.T) {
codec := amino.NewCodec()
cmd := GetBroadcastCommand(codec)
viper.Set(flags.FlagOffline, false)
testDir, cleanFunc := tests.NewTestCaseDir(t)
t.Cleanup(cleanFunc)
// Create new file with tx
txContents := []byte("{\"type\":\"cosmos-sdk/StdTx\",\"value\":{\"msg\":[{\"type\":\"cosmos-sdk/MsgSend\",\"value\":{\"from_address\":\"cosmos1cxlt8kznps92fwu3j6npahx4mjfutydyene2qw\",\"to_address\":\"cosmos1wc8mpr8m3sy3ap3j7fsgqfzx36um05pystems4\",\"amount\":[{\"denom\":\"stake\",\"amount\":\"10000\"}]}}],\"fee\":{\"amount\":[],\"gas\":\"200000\"},\"signatures\":null,\"memo\":\"\"}}")
txFileName := filepath.Join(testDir, "tx.json")
err := ioutil.WriteFile(txFileName, txContents, 0644)
require.NoError(t, err)
err = cmd.RunE(cmd, []string{txFileName})
// We test it tries to broadcast but we set unsupported tx to get the error.
require.EqualError(t, err, "unsupported return type ; supported types: sync, async, block")
}

View File

@ -51,7 +51,6 @@ recommended to set such parameters manually.
}
cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit")
cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query a full node")
cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT")
// Add the flags here and return the command
@ -85,7 +84,7 @@ func makeMultiSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string)
cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
txBldr := types.NewTxBuilderFromCLI(inBuf)
if !viper.GetBool(flagOffline) {
if !cliCtx.Offline {
accnum, seq, err := types.NewAccountRetriever(client.Codec, cliCtx).GetAccountNumberSequence(multisigInfo.GetAddress())
if err != nil {
return err

View File

@ -22,7 +22,6 @@ const (
flagMultisig = "multisig"
flagAppend = "append"
flagValidateSigs = "validate-signatures"
flagOffline = "offline"
flagSigOnly = "signature-only"
flagOutfile = "output-document"
)
@ -71,12 +70,7 @@ be generated via the 'multisign' command.
"Print the addresses that must sign the transaction, those who have already signed it, and make sure that signatures are in the correct order",
)
cmd.Flags().Bool(flagSigOnly, false, "Print only the generated signature, then exit")
cmd.Flags().Bool(
flagOffline, false,
"Offline mode; Do not query a full node. --account and --sequence options would be required if offline is set",
)
cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT")
cmd = flags.PostCommands(cmd)[0]
cmd.MarkFlagRequired(flags.FlagFrom)
@ -86,7 +80,7 @@ be generated via the 'multisign' command.
func preSignCmd(cmd *cobra.Command, _ []string) {
// Conditionally mark the account and sequence numbers required as no RPC
// query will be done.
if viper.GetBool(flagOffline) {
if viper.GetBool(flags.FlagOffline) {
cmd.MarkFlagRequired(flags.FlagAccountNumber)
cmd.MarkFlagRequired(flags.FlagSequence)
}
@ -100,12 +94,11 @@ func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error
}
inBuf := bufio.NewReader(cmd.InOrStdin())
offline := viper.GetBool(flagOffline)
cliCtx := context.NewCLIContextWithInput(inBuf).WithCodec(cdc)
txBldr := types.NewTxBuilderFromCLI(inBuf)
if viper.GetBool(flagValidateSigs) {
if !printAndValidateSigs(cliCtx, txBldr.ChainID(), stdTx, offline) {
if !printAndValidateSigs(cliCtx, txBldr.ChainID(), stdTx, cliCtx.Offline) {
return fmt.Errorf("signatures validation failed")
}
@ -124,14 +117,13 @@ func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error
if err != nil {
return err
}
newTx, err = client.SignStdTxWithSignerAddress(
txBldr, cliCtx, multisigAddr, cliCtx.GetFromName(), stdTx, offline,
txBldr, cliCtx, multisigAddr, cliCtx.GetFromName(), stdTx, cliCtx.Offline,
)
generateSignatureOnly = true
} else {
appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly
newTx, err = client.SignStdTx(txBldr, cliCtx, cliCtx.GetFromName(), stdTx, appendSig, offline)
newTx, err = client.SignStdTx(txBldr, cliCtx, cliCtx.GetFromName(), stdTx, appendSig, cliCtx.Offline)
}
if err != nil {

View File

@ -327,8 +327,8 @@ func PrepareTxBuilder(txBldr authtypes.TxBuilder, cliCtx context.CLIContext) (au
func buildUnsignedStdTxOffline(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx authtypes.StdTx, err error) {
if txBldr.SimulateAndExecute() {
if cliCtx.GenerateOnly {
return stdTx, errors.New("cannot estimate gas with generate-only")
if cliCtx.Offline {
return stdTx, errors.New("cannot estimate gas in offline mode")
}
txBldr, err = EnrichWithGas(txBldr, cliCtx, msgs)

View File

@ -150,8 +150,8 @@ $ %s tx distribution withdraw-all-rewards --from mykey
// The transaction cannot be generated offline since it requires a query
// to get all the validators.
if cliCtx.GenerateOnly {
return fmt.Errorf("command disabled with the provided flag: %s", flags.FlagGenerateOnly)
if cliCtx.Offline {
return fmt.Errorf("cannot generate tx in offline mode")
}
msgs, err := common.WithdrawAllDelegatorRewards(cliCtx, queryRoute, delAddr)