Skip to content

Commit

Permalink
feat: add cli for tips transactions (#10311)
Browse files Browse the repository at this point in the history
<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

Closes: #10264 

This PR makes 2 changes in the CLI:

### 1. Add an `--aux` flag to all tx command.

```bash
simd tx bank send <from> <to> <amount> --aux (optional: --tip <tipAmt> --tipper <tipper>)
```

This will print an AuxSignerData instead of broadcasting the tx.

### 2. Add a new `aux-to-fee` subcommand

```bash
simd tx aux-to-fee <aux_signer_data.json>
```

This takes the output of the previous command, and broadcasts a 2-signer tx.



<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review. -->

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
atheeshp authored Dec 7, 2021
1 parent 779c7e2 commit 5f840be
Show file tree
Hide file tree
Showing 17 changed files with 598 additions and 20 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10208](https://github.com/cosmos/cosmos-sdk/pull/10208) Add `TipsTxMiddleware` for transferring tips.
* [\#10379](https://github.com/cosmos/cosmos-sdk/pull/10379) Add validation to `x/upgrade` CLI `software-upgrade` command `--plan-info` value.
* [\#10561](https://github.com/cosmos/cosmos-sdk/pull/10561) Add configurable IAVL cache size to app.toml
* [\10507](https://github.com/cosmos/cosmos-sdk/pull/10507) Add middleware for tx priority.
* [\#10507](https://github.com/cosmos/cosmos-sdk/pull/10507) Add middleware for tx priority.
* [\#10311](https://github.com/cosmos/cosmos-sdk/pull/10311) Adds cli to use tips transactions. It adds an `--aux` flag to all CLI tx commands to generate the aux signer data (with optional tip), and a new `tx aux-to-fee` subcommand to let the fee payer gather aux signer data and broadcast the tx

### Improvements

Expand Down
18 changes: 18 additions & 0 deletions client/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,24 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err
}
}

if !clientCtx.IsAux || flagSet.Changed(flags.FlagAux) {
isAux, _ := flagSet.GetBool(flags.FlagAux)
clientCtx = clientCtx.WithAux(isAux)
if isAux {
// If the user didn't explicity set an --output flag, use JSON by
// default.
if clientCtx.OutputFormat == "" || !flagSet.Changed(cli.OutputFlag) {
clientCtx = clientCtx.WithOutputFormat("json")
}

// If the user didn't explicity set a --sign-mode flag, use
// DIRECT_AUX by default.
if clientCtx.SignModeStr == "" || !flagSet.Changed(flags.FlagSignMode) {
clientCtx = clientCtx.WithSignModeStr(flags.SignModeDirectAux)
}
}
}

return clientCtx, nil
}

Expand Down
9 changes: 9 additions & 0 deletions client/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type Context struct {
FeePayer sdk.AccAddress
FeeGranter sdk.AccAddress
Viper *viper.Viper

// IsAux is true when the signer is an auxiliary signer (e.g. the tipper).
IsAux bool

// TODO: Deprecated (remove).
LegacyAmino *codec.LegacyAmino
Expand Down Expand Up @@ -245,6 +248,12 @@ func (ctx Context) WithViper(prefix string) Context {
return ctx
}

// WithAux returns a copy of the context with an updated IsAux value.
func (ctx Context) WithAux(isAux bool) Context {
ctx.IsAux = isAux
return ctx
}

// PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout
func (ctx Context) PrintString(str string) error {
return ctx.PrintBytes([]byte(str))
Expand Down
10 changes: 9 additions & 1 deletion client/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const (
SignModeDirect = "direct"
// SignModeLegacyAminoJSON is the value of the --sign-mode flag for SIGN_MODE_LEGACY_AMINO_JSON
SignModeLegacyAminoJSON = "amino-json"
// SignModeDirectAux is the value of the --sign-mode flag for SIGN_MODE_DIRECT_AUX
SignModeDirectAux = "direct-aux"
)

// List of CLI flags
Expand Down Expand Up @@ -73,6 +75,9 @@ const (
FlagFeePayer = "fee-payer"
FlagFeeGranter = "fee-granter"
FlagReverse = "reverse"
FlagTip = "tip"
FlagAux = "aux"
FlagTipper = "tipper"

// Tendermint logging flags
FlagLogLevel = "log_level"
Expand Down Expand Up @@ -111,10 +116,13 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().Bool(FlagOffline, false, "Offline mode (does not allow any online functionality)")
cmd.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation")
cmd.Flags().String(FlagKeyringBackend, DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test|memory)")
cmd.Flags().String(FlagSignMode, "", "Choose sign mode (direct|amino-json), this is an advanced feature")
cmd.Flags().String(FlagSignMode, "", "Choose sign mode (direct|amino-json|direct-aux), this is an advanced feature")
cmd.Flags().Uint64(FlagTimeoutHeight, 0, "Set a block timeout height to prevent the tx from being committed past a certain height")
cmd.Flags().String(FlagFeePayer, "", "Fee payer pays fees for the transaction instead of deducting from the signer")
cmd.Flags().String(FlagFeeGranter, "", "Fee granter grants fees for the transaction")
cmd.Flags().String(FlagTip, "", "Tip is the amount that is going to be transferred to the fee payer on the target chain. This flag is only valid when used with --aux")
cmd.Flags().String(FlagTipper, "", "Tipper will pay for tips for executing the tx on the target chain. This flag is only valid when used with --aux")
cmd.Flags().Bool(FlagAux, false, "Generate aux signer data instead of sending a tx")

// --gas can accept integers and "auto"
cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically (default %d)", GasFlagAuto, DefaultGasLimit))
Expand Down
26 changes: 24 additions & 2 deletions client/tx/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)

Expand All @@ -29,6 +30,7 @@ type Factory struct {
chainID string
memo string
fees sdk.Coins
tip *tx.Tip
gasPrices sdk.DecCoins
signMode signing.SignMode
simulateAndExecute bool
Expand All @@ -44,6 +46,8 @@ func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory {
signMode = signing.SignMode_SIGN_MODE_DIRECT
case flags.SignModeLegacyAminoJSON:
signMode = signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
case flags.SignModeDirectAux:
signMode = signing.SignMode_SIGN_MODE_DIRECT_AUX
}

accNum, _ := flagSet.GetUint64(flags.FlagAccountNumber)
Expand Down Expand Up @@ -73,6 +77,10 @@ func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory {
feesStr, _ := flagSet.GetString(flags.FlagFees)
f = f.WithFees(feesStr)

tipsStr, _ := flagSet.GetString(flags.FlagTip)
tipper, _ := flagSet.GetString(flags.FlagTipper)
f = f.WithTips(tipsStr, tipper)

gasPricesStr, _ := flagSet.GetString(flags.FlagGasPrices)
f = f.WithGasPrices(gasPricesStr)

Expand Down Expand Up @@ -130,6 +138,20 @@ func (f Factory) WithFees(fees string) Factory {
return f
}

// WithTips returns a copy of the Factory with an updated tip.
func (f Factory) WithTips(tip string, tipper string) Factory {
parsedTips, err := sdk.ParseCoinsNormalized(tip)
if err != nil {
panic(err)
}

f.tip = &tx.Tip{
Tipper: tipper,
Amount: parsedTips,
}
return f
}

// WithGasPrices returns a copy of the Factory with updated gas prices.
func (f Factory) WithGasPrices(gasPrices string) Factory {
parsedGasPrices, err := sdk.ParseDecCoins(gasPrices)
Expand Down Expand Up @@ -254,12 +276,12 @@ func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) erro
_, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()})
}

tx, err := f.BuildUnsignedTx(msgs...)
unsignedTx, err := f.BuildUnsignedTx(msgs...)
if err != nil {
return err
}

json, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx())
json, err := clientCtx.TxConfig.TxJSONEncoder()(unsignedTx.GetTx())
if err != nil {
return err
}
Expand Down
83 changes: 83 additions & 0 deletions client/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
// or sign it and broadcast it returning an error upon failure.
func GenerateOrBroadcastTxCLI(clientCtx client.Context, flagSet *pflag.FlagSet, msgs ...sdk.Msg) error {
txf := NewFactoryCLI(clientCtx, flagSet)

return GenerateOrBroadcastTxWithFactory(clientCtx, txf, msgs...)
}

Expand All @@ -40,6 +41,16 @@ func GenerateOrBroadcastTxWithFactory(clientCtx client.Context, txf Factory, msg
}
}

// If the --aux flag is set, we simply generate and print the AuxSignerData.
if clientCtx.IsAux {
auxSignerData, err := makeAuxSignerData(clientCtx, txf, msgs...)
if err != nil {
return err
}

return clientCtx.PrintProto(&auxSignerData)
}

if clientCtx.GenerateOnly {
return txf.PrintUnsignedTx(clientCtx, msgs...)
}
Expand Down Expand Up @@ -335,3 +346,75 @@ type GasEstimateResponse struct {
func (gr GasEstimateResponse) String() string {
return fmt.Sprintf("gas estimate: %d", gr.GasEstimate)
}

// makeAuxSignerData generates an AuxSignerData from the client inputs.
func makeAuxSignerData(clientCtx client.Context, f Factory, msgs ...sdk.Msg) (tx.AuxSignerData, error) {
b := NewAuxTxBuilder()
fromAddress, name, _, err := client.GetFromFields(clientCtx.Keyring, clientCtx.From, false)
if err != nil {
return tx.AuxSignerData{}, err
}

b.SetAddress(fromAddress.String())
if clientCtx.Offline {
b.SetAccountNumber(f.accountNumber)
b.SetSequence(f.sequence)
} else {
accNum, seq, err := clientCtx.AccountRetriever.GetAccountNumberSequence(clientCtx, fromAddress)
if err != nil {
return tx.AuxSignerData{}, err
}
b.SetAccountNumber(accNum)
b.SetSequence(seq)
}

err = b.SetMsgs(msgs...)
if err != nil {
return tx.AuxSignerData{}, err
}

if f.tip != nil {
if f.tip.Tipper == "" {
return tx.AuxSignerData{}, sdkerrors.Wrap(errors.New("tipper flag required"), "tipper")
} else {
if _, err := sdk.AccAddressFromBech32(f.tip.Tipper); err != nil {
return tx.AuxSignerData{}, sdkerrors.ErrInvalidAddress.Wrap("tipper must be a bech32 address")
}
b.SetTip(f.tip)
}
}

err = b.SetSignMode(f.SignMode())
if err != nil {
return tx.AuxSignerData{}, err
}

key, err := clientCtx.Keyring.Key(name)
if err != nil {
return tx.AuxSignerData{}, err
}

pub, err := key.GetPubKey()
if err != nil {
return tx.AuxSignerData{}, err
}

err = b.SetPubKey(pub)
if err != nil {
return tx.AuxSignerData{}, err
}

b.SetChainID(clientCtx.ChainID)
signBz, err := b.GetSignBytes()
if err != nil {
return tx.AuxSignerData{}, err
}

sig, _, err := clientCtx.Keyring.Sign(name, signBz)
if err != nil {
return tx.AuxSignerData{}, err
}
b.SetSignature(sig)

return b.GetAuxSignerData()
}
1 change: 1 addition & 0 deletions client/tx/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ func TestSign(t *testing.T) {
{"direct: should overwrite multi-signers tx with DIRECT sig",
txfDirect, txb2, from1, true, []cryptotypes.PubKey{pubKey1}, nil},
}

var prevSigs []signingtypes.SignatureV2
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion client/tx_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ type (
SetSignatures(signatures ...signingtypes.SignatureV2) error
SetMemo(memo string)
SetFeeAmount(amount sdk.Coins)
SetFeePayer(feePayer sdk.AccAddress)
SetGasLimit(limit uint64)
SetTip(tip *tx.Tip)
SetTimeoutHeight(height uint64)
SetFeePayer(feePayer sdk.AccAddress)
SetFeeGranter(feeGranter sdk.AccAddress)
AddAuxSignerData(tx.AuxSignerData) error
}
Expand Down
1 change: 1 addition & 0 deletions simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ func txCommand() *cobra.Command {
authcmd.GetBroadcastCommand(),
authcmd.GetEncodeCommand(),
authcmd.GetDecodeCommand(),
authcmd.GetAuxToFeeCommand(),
)

simapp.ModuleBasics.AddTxCommands(cmd)
Expand Down
85 changes: 85 additions & 0 deletions x/auth/client/cli/tips.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cli

import (
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/types/tx"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
)

func GetAuxToFeeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "aux-to-fee <aux_signed_tx.json>",
Short: "includes the aux signer data in the tx, broadcast the tx, and sends the tip amount to the broadcaster",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

auxSignerData := tx.AuxSignerData{}
err = readAuxSignerData(clientCtx.Codec, &auxSignerData, args[0])
if err != nil {
return err
}

f := clienttx.NewFactoryCLI(clientCtx, cmd.Flags())

txBuilder := clientCtx.TxConfig.NewTxBuilder()
err = txBuilder.AddAuxSignerData(auxSignerData)
if err != nil {
return err
}

txBuilder.SetFeePayer(clientCtx.FromAddress)
txBuilder.SetFeeAmount(f.Fees())
txBuilder.SetGasLimit(f.Gas())

if clientCtx.GenerateOnly {
json, err := clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
if err != nil {
return err
}
return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
}

err = authclient.SignTx(f, clientCtx, clientCtx.FromName, txBuilder, clientCtx.Offline, false)
if err != nil {
return err
}

txBytes, err := clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return err
}

// broadcast to a Tendermint node
res, err := clientCtx.BroadcastTx(txBytes)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddTxFlagsToCmd(cmd)
return cmd
}

func readAuxSignerData(cdc codec.Codec, auxSignerData *tx.AuxSignerData, filename string) error {
bytes, err := os.ReadFile(filename)
if err != nil {
return err
}

return cdc.UnmarshalJSON(bytes, auxSignerData)
}
9 changes: 9 additions & 0 deletions x/auth/client/testutil/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,13 @@ func TxMultiSignBatchExec(clientCtx client.Context, filename string, from string
return clitestutil.ExecTestCLICmd(clientCtx, cli.GetMultiSignBatchCmd(), args)
}

// TxAuxToFeeExec executes `GetAuxToFeeCommand` cli command with given args.
func TxAuxToFeeExec(clientCtx client.Context, filename string, extraArgs ...string) (testutil.BufferWriter, error) {
args := []string{
filename,
}

return clitestutil.ExecTestCLICmd(clientCtx, cli.GetAuxToFeeCommand(), append(args, extraArgs...))
}

// DONTCOVER
Loading

0 comments on commit 5f840be

Please sign in to comment.