Merge PR #3970: Fix Tx Sign Offline Mode
- Add shorthand flags `-a` and `-s` for the account and sequence numbers respectively - Mark the account and sequence numbers required during "offline" mode - Always do an RPC query for account and sequence number during "online" mode - If clients wish to provide such values, they must use `--offline`. This makes the whole flow/UX easier to reason about. closes: #3893
This commit is contained in:
parent
83f3d1e192
commit
ea46da7126
|
@ -0,0 +1,4 @@
|
||||||
|
#3893 Improve `gaiacli tx sign` command
|
||||||
|
* Add shorthand flags -a and -s for the account and sequence numbers respectively
|
||||||
|
* Mark the account and sequence numbers required during "offline" mode
|
||||||
|
* Always do an RPC query for account and sequence number during "online" mode
|
|
@ -81,8 +81,8 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||||
for _, c := range cmds {
|
for _, c := range cmds {
|
||||||
c.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response")
|
c.Flags().Bool(FlagIndentResponse, false, "Add indent to JSON response")
|
||||||
c.Flags().String(FlagFrom, "", "Name or address of private key with which to sign")
|
c.Flags().String(FlagFrom, "", "Name or address of private key with which to sign")
|
||||||
c.Flags().Uint64(FlagAccountNumber, 0, "AccountNumber number to sign the tx")
|
c.Flags().Uint64P(FlagAccountNumber, "a", 0, "The account number number of the signing account (offline mode only)")
|
||||||
c.Flags().Uint64(FlagSequence, 0, "Sequence number to sign the tx")
|
c.Flags().Uint64P(FlagSequence, "s", 0, "The sequence number of the signing account (offline mode only)")
|
||||||
c.Flags().String(FlagMemo, "", "Memo to send along with transaction")
|
c.Flags().String(FlagMemo, "", "Memo to send along with transaction")
|
||||||
c.Flags().String(FlagFees, "", "Fees to pay along with transaction; eg: 10uatom")
|
c.Flags().String(FlagFees, "", "Fees to pay along with transaction; eg: 10uatom")
|
||||||
c.Flags().String(FlagGasPrices, "", "Gas prices to determine the transaction fee (e.g. 10uatom)")
|
c.Flags().String(FlagGasPrices, "", "Gas prices to determine the transaction fee (e.g. 10uatom)")
|
||||||
|
|
|
@ -164,15 +164,17 @@ func PrintUnsignedStdTx(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignStdTx appends a signature to a StdTx and returns a copy of a it. If appendSig
|
// SignStdTx appends a signature to a StdTx and returns a copy of it. If appendSig
|
||||||
// is false, it replaces the signatures already attached with the new signature.
|
// is false, it replaces the signatures already attached with the new signature.
|
||||||
// Don't perform online validation or lookups if offline is true.
|
// Don't perform online validation or lookups if offline is true.
|
||||||
func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) {
|
func SignStdTx(
|
||||||
|
txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string,
|
||||||
|
stdTx auth.StdTx, appendSig bool, offline bool,
|
||||||
|
) (auth.StdTx, error) {
|
||||||
|
|
||||||
var signedStdTx auth.StdTx
|
var signedStdTx auth.StdTx
|
||||||
|
|
||||||
keybase := txBldr.Keybase()
|
info, err := txBldr.Keybase().Get(name)
|
||||||
|
|
||||||
info, err := keybase.Get(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return signedStdTx, err
|
return signedStdTx, err
|
||||||
}
|
}
|
||||||
|
@ -185,8 +187,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !offline {
|
if !offline {
|
||||||
txBldr, err = populateAccountFromState(
|
txBldr, err = populateAccountFromState(txBldr, cliCtx, sdk.AccAddress(addr))
|
||||||
txBldr, cliCtx, sdk.AccAddress(addr))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return signedStdTx, err
|
return signedStdTx, err
|
||||||
}
|
}
|
||||||
|
@ -244,25 +245,21 @@ func ReadStdTxFromFile(cdc *amino.Codec, filename string) (stdTx auth.StdTx, err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateAccountFromState(txBldr authtxb.TxBuilder, cliCtx context.CLIContext,
|
func populateAccountFromState(
|
||||||
addr sdk.AccAddress) (authtxb.TxBuilder, error) {
|
txBldr authtxb.TxBuilder, cliCtx context.CLIContext, addr sdk.AccAddress,
|
||||||
if txBldr.AccountNumber() == 0 {
|
) (authtxb.TxBuilder, error) {
|
||||||
accNum, err := cliCtx.GetAccountNumber(addr)
|
|
||||||
if err != nil {
|
accNum, err := cliCtx.GetAccountNumber(addr)
|
||||||
return txBldr, err
|
if err != nil {
|
||||||
}
|
return txBldr, err
|
||||||
txBldr = txBldr.WithAccountNumber(accNum)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if txBldr.Sequence() == 0 {
|
accSeq, err := cliCtx.GetAccountSequence(addr)
|
||||||
accSeq, err := cliCtx.GetAccountSequence(addr)
|
if err != nil {
|
||||||
if err != nil {
|
return txBldr, err
|
||||||
return txBldr, err
|
|
||||||
}
|
|
||||||
txBldr = txBldr.WithSequence(accSeq)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return txBldr, nil
|
return txBldr.WithAccountNumber(accNum).WithSequence(accSeq), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTxEncoder return tx encoder from global sdk configuration if ones is defined.
|
// GetTxEncoder return tx encoder from global sdk configuration if ones is defined.
|
||||||
|
|
|
@ -27,33 +27,35 @@ const (
|
||||||
flagOutfile = "output-document"
|
flagOutfile = "output-document"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSignCommand returns the sign command
|
// GetSignCommand returns the transaction sign command.
|
||||||
func GetSignCommand(codec *amino.Codec) *cobra.Command {
|
func GetSignCommand(codec *amino.Codec) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "sign [file]",
|
Use: "sign [file]",
|
||||||
Short: "Sign transactions generated offline",
|
Short: "Sign transactions generated offline",
|
||||||
Long: `Sign transactions created with the --generate-only flag.
|
Long: `Sign transactions created with the --generate-only flag.
|
||||||
Read a transaction from [file], sign it, and print its JSON encoding.
|
It will read a transaction from [file], sign it, and print its JSON encoding.
|
||||||
|
|
||||||
If the flag --signature-only flag is on, it outputs a JSON representation
|
If the flag --signature-only flag is set, it will output a JSON representation
|
||||||
of the generated signature only.
|
of the generated signature only.
|
||||||
|
|
||||||
If the flag --validate-signatures is on, then the command would check whether all required
|
If the flag --validate-signatures is set, then the command would check whether all required
|
||||||
signers have signed the transactions, whether the signatures were collected in the right
|
signers have signed the transactions, whether the signatures were collected in the right
|
||||||
order, and if the signature is valid over the given transaction. If the --offline
|
order, and if the signature is valid over the given transaction. If the --offline
|
||||||
flag is also provided, signature validation over the transaction will be not be
|
flag is also set, signature validation over the transaction will be not be
|
||||||
performed as that will require communication with a full node.
|
performed as that will require RPC communication with a full node.
|
||||||
|
|
||||||
The --offline flag makes sure that the client will not reach out to an external node.
|
The --offline flag makes sure that the client will not reach out to full node.
|
||||||
Thus account number or sequence number lookups will not be performed and it is
|
As a result, the account and sequence number queries will not be performed and
|
||||||
recommended to set such parameters manually.
|
it is required to set such parameters manually. Note, invalid values will cause
|
||||||
|
the transaction to fail.
|
||||||
|
|
||||||
The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account
|
The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account
|
||||||
key. It implies --signature-only. Full multisig signed transactions may eventually
|
key. It implies --signature-only. Full multisig signed transactions may eventually
|
||||||
be generated via the 'multisign' command.
|
be generated via the 'multisign' command.
|
||||||
`,
|
`,
|
||||||
RunE: makeSignCmd(codec),
|
PreRun: preSignCmd,
|
||||||
Args: cobra.ExactArgs(1),
|
RunE: makeSignCmd(codec),
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().String(
|
cmd.Flags().String(
|
||||||
|
@ -69,11 +71,22 @@ 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",
|
"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(flagSigOnly, false, "Print only the generated signature, then exit")
|
||||||
cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query a full node")
|
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")
|
cmd.Flags().String(flagOutfile, "", "The document will be written to the given file instead of STDOUT")
|
||||||
|
|
||||||
// add the flags here and return the command
|
cmd = client.PostCommands(cmd)[0]
|
||||||
return client.PostCommands(cmd)[0]
|
cmd.MarkFlagRequired(client.FlagFrom)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
cmd.MarkFlagRequired(client.FlagAccountNumber)
|
||||||
|
cmd.MarkFlagRequired(client.FlagSequence)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error {
|
func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -95,11 +108,6 @@ func makeSignCmd(cdc *amino.Codec) func(cmd *cobra.Command, args []string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
from := viper.GetString(client.FlagFrom)
|
|
||||||
if from == "" {
|
|
||||||
return fmt.Errorf("required flag '%s' has not been set", client.FlagFrom)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if --signature-only is on, then override --append
|
// if --signature-only is on, then override --append
|
||||||
var newTx auth.StdTx
|
var newTx auth.StdTx
|
||||||
generateSignatureOnly := viper.GetBool(flagSigOnly)
|
generateSignatureOnly := viper.GetBool(flagSigOnly)
|
||||||
|
|
|
@ -245,7 +245,7 @@ func (bldr TxBuilder) BuildTxForSim(msgs []sdk.Msg) ([]byte, error) {
|
||||||
return bldr.txEncoder(auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, signMsg.Memo))
|
return bldr.txEncoder(auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, signMsg.Memo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignStdTx appends a signature to a StdTx and returns a copy of a it. If append
|
// SignStdTx appends a signature to a StdTx and returns a copy of it. If append
|
||||||
// is false, it replaces the signatures already attached with the new signature.
|
// is false, it replaces the signatures already attached with the new signature.
|
||||||
func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appendSig bool) (signedStdTx auth.StdTx, err error) {
|
func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appendSig bool) (signedStdTx auth.StdTx, err error) {
|
||||||
stdSignature, err := MakeSignature(bldr.keybase, name, passphrase, StdSignMsg{
|
stdSignature, err := MakeSignature(bldr.keybase, name, passphrase, StdSignMsg{
|
||||||
|
|
|
@ -62,5 +62,9 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
|
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return client.PostCommands(cmd)[0]
|
|
||||||
|
cmd = client.PostCommands(cmd)[0]
|
||||||
|
cmd.MarkFlagRequired(client.FlagFrom)
|
||||||
|
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue