feat: add cli for tips transactions (#10311)

<!--
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)
This commit is contained in:
atheeshp 2021-12-07 23:32:21 +05:30 committed by GitHub
parent 779c7e2e17
commit 5f840be76a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 598 additions and 20 deletions

View File

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

View File

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

View File

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

View File

@ -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
@ -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"
@ -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))

View File

@ -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"
)
@ -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
@ -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)
@ -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)
@ -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)
@ -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
}

View File

@ -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...)
}
@ -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...)
}
@ -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()
}

View File

@ -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) {

View File

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

View File

@ -215,6 +215,7 @@ func txCommand() *cobra.Command {
authcmd.GetBroadcastCommand(),
authcmd.GetEncodeCommand(),
authcmd.GetDecodeCommand(),
authcmd.GetAuxToFeeCommand(),
)
simapp.ModuleBasics.AddTxCommands(cmd)

85
x/auth/client/cli/tips.go Normal file
View File

@ -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)
}

View File

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

View File

@ -34,6 +34,8 @@ import (
bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
govtestutil "github.com/cosmos/cosmos-sdk/x/gov/client/testutil"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
type IntegrationTestSuite struct {
@ -898,6 +900,7 @@ func (s *IntegrationTestSuite) TestCLIMultisignSortSignatures() {
s.Require().NoError(err)
intialCoins := balRes.Balances
s.Require().NoError(s.network.WaitForNextBlock())
// Send coins from validator to multisig.
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10)
_, err = s.createBankMsg(
@ -986,10 +989,12 @@ func (s *IntegrationTestSuite) TestCLIMultisign() {
// Send coins from validator to multisig.
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10)
s.Require().NoError(s.network.WaitForNextBlock())
_, err = s.createBankMsg(
val1, addr,
sdk.NewCoins(sendTokens),
)
s.Require().NoError(s.network.WaitForNextBlock())
s.Require().NoError(err)
s.Require().NoError(s.network.WaitForNextBlock())
@ -999,7 +1004,7 @@ func (s *IntegrationTestSuite) TestCLIMultisign() {
var balRes banktypes.QueryAllBalancesResponse
err = val1.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &balRes)
s.Require().NoError(err)
s.Require().Equal(sendTokens.Amount, balRes.Balances.AmountOf(s.cfg.BondDenom))
s.Require().True(sendTokens.Amount.Equal(balRes.Balances.AmountOf(s.cfg.BondDenom)))
// Generate multisig transaction.
multiGeneratedTx, err := bankcli.MsgSendExec(
@ -1117,7 +1122,6 @@ func (s *IntegrationTestSuite) TestSignBatchMultisig() {
file2 := testutil.WriteToNewTempFile(s.T(), res.String())
_, err = TxMultiSignExec(val.ClientCtx, multisigRecord.Name, filename.Name(), file1.Name(), file2.Name())
s.Require().NoError(err)
}
func (s *IntegrationTestSuite) TestMultisignBatch() {
@ -1460,6 +1464,333 @@ func (s *IntegrationTestSuite) TestSignWithMultiSignersAminoJSON() {
require.Equal(sdk.NewCoins(val0Coin, val1Coin), queryRes.Balances)
}
func (s *IntegrationTestSuite) TestAuxSigner() {
require := s.Require()
val := s.network.Validators[0]
val0Coin := sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10))
testCases := []struct {
name string
args []string
expectErr bool
}{
{
"error with SIGN_MODE_DIRECT_AUX and --aux unset",
[]string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
},
true,
},
{
"no error with SIGN_MDOE_DIRECT_AUX mode and generate-only set (ignores generate-only)",
[]string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly),
},
false,
},
{
"no error with SIGN_MDOE_DIRECT_AUX mode and generate-only, tip flag set",
[]string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly),
fmt.Sprintf("--%s=%s", flags.FlagTip, val0Coin.String()),
},
false,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
_, err := govtestutil.MsgSubmitProposal(
val.ClientCtx,
val.Address.String(),
"test",
"test desc",
govtypes.ProposalTypeText,
tc.args...,
)
if tc.expectErr {
require.Error(err)
} else {
require.NoError(err)
}
})
}
}
func (s *IntegrationTestSuite) TestAuxToFee() {
require := s.Require()
val := s.network.Validators[0]
kb := s.network.Validators[0].ClientCtx.Keyring
acc, _, err := kb.NewMnemonic("tipperAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
require.NoError(err)
tipper, err := acc.GetAddress()
require.NoError(err)
tipperInitialBal := sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10000))
feePayer := val.Address
fee := sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(1000))
tip := sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(1000))
s.Require().NoError(s.network.WaitForNextBlock())
_, err = s.createBankMsg(val, tipper, sdk.NewCoins(tipperInitialBal))
require.NoError(err)
s.Require().NoError(s.network.WaitForNextBlock())
bal := s.getBalances(val.ClientCtx, tipper, tip.Denom)
s.Require().True(bal.Equal(tipperInitialBal.Amount))
testCases := []struct {
name string
tipper sdk.AccAddress
feePayer sdk.AccAddress
tip sdk.Coin
expectErrAux bool
expectErrBroadCast bool
errMsg string
tipperArgs []string
feePayerArgs []string
}{
{
name: "when --aux and --sign-mode = direct set: error",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
expectErrAux: true,
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "when --aux and --sign-mode = direct set: error",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
expectErrAux: true,
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "both tipper, fee payer uses AMINO: no error",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "tipper uses DIRECT_AUX, fee payer uses AMINO: no error",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "--tip flag unset: no error",
tipper: tipper,
feePayer: feePayer,
tip: sdk.Coin{Denom: fmt.Sprintf("%stoken", val.Moniker), Amount: sdk.NewInt(0)},
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "legacy amino json: no error",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "tipper uses direct aux, fee payer uses direct: happy case",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "wrong tipper address: error",
tipper: tipper,
feePayer: feePayer,
tip: tip,
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagTipper, "foobar"),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
expectErrAux: true,
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
},
{
name: "wrong denom in tip: error",
tipper: tipper,
feePayer: feePayer,
tip: sdk.Coin{Denom: fmt.Sprintf("%stoken", val.Moniker), Amount: sdk.NewInt(0)},
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagTip, "1000wrongDenom"),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()),
},
errMsg: "insufficient funds",
},
{
name: "insufficient fees: error",
tipper: tipper,
feePayer: feePayer,
tip: sdk.Coin{Denom: fmt.Sprintf("%stoken", val.Moniker), Amount: sdk.NewInt(0)},
tipperArgs: []string{
fmt.Sprintf("--%s=%s", flags.FlagTip, tip),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux),
fmt.Sprintf("--%s=%s", flags.FlagTipper, tipper.String()),
fmt.Sprintf("--%s=true", flags.FlagAux),
},
feePayerArgs: []string{
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer),
},
errMsg: "insufficient fees",
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
res, err := govtestutil.MsgSubmitProposal(
val.ClientCtx,
tipper.String(),
"test",
"test desc",
govtypes.ProposalTypeText,
tc.tipperArgs...,
)
if tc.expectErrAux {
require.Error(err)
} else {
require.NoError(err)
genTxFile := testutil.WriteToNewTempFile(s.T(), string(res.Bytes()))
// broadcast the tx
res, err = TxAuxToFeeExec(
val.ClientCtx,
genTxFile.Name(),
tc.feePayerArgs...,
)
var txRes sdk.TxResponse
s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(res.Bytes(), &txRes))
if tc.expectErrBroadCast {
require.Error(err)
} else if tc.errMsg != "" {
require.Contains(txRes.RawLog, tc.errMsg)
} else {
require.NoError(err)
s.Require().Equal(uint32(0), txRes.Code)
s.Require().NotNil(int64(0), txRes.Height)
bal = s.getBalances(val.ClientCtx, tipper, tc.tip.Denom)
tipperInitialBal = tipperInitialBal.Sub(tc.tip)
s.Require().True(bal.Equal(tipperInitialBal.Amount))
}
}
})
}
}
func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress, amount sdk.Coins, extraFlags ...string) (testutil.BufferWriter, error) {
flags := []string{fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
@ -1470,3 +1801,14 @@ func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.
flags = append(flags, extraFlags...)
return bankcli.MsgSendExec(val.ClientCtx, val.Address, toAddr, amount, flags...)
}
func (s *IntegrationTestSuite) getBalances(clientCtx client.Context, addr sdk.AccAddress, denom string) sdk.Int {
resp, err := bankcli.QueryBalancesExec(clientCtx, addr)
s.Require().NoError(err)
var balRes banktypes.QueryAllBalancesResponse
err = clientCtx.Codec.UnmarshalJSON(resp.Bytes(), &balRes)
s.Require().NoError(err)
startTokens := balRes.Balances.AmountOf(denom)
return startTokens
}

View File

@ -7,7 +7,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)
@ -107,28 +106,28 @@ func (tx StdTx) GetMsgs() []sdk.Msg { return tx.Msgs }
// ValidateBasic does a simple and lightweight validation check that doesn't
// require access to any other information.
func (tx StdTx) ValidateBasic() error {
stdSigs := tx.GetSignatures()
func (stdTx StdTx) ValidateBasic() error {
stdSigs := stdTx.GetSignatures()
if tx.Fee.Gas > txtypes.MaxGasWanted {
if stdTx.Fee.Gas > tx.MaxGasWanted {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest,
"invalid gas supplied; %d > %d", tx.Fee.Gas, txtypes.MaxGasWanted,
"invalid gas supplied; %d > %d", stdTx.Fee.Gas, tx.MaxGasWanted,
)
}
if tx.Fee.Amount.IsAnyNegative() {
if stdTx.Fee.Amount.IsAnyNegative() {
return sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"invalid fee provided: %s", tx.Fee.Amount,
"invalid fee provided: %s", stdTx.Fee.Amount,
)
}
if len(stdSigs) == 0 {
return sdkerrors.ErrNoSignatures
}
if len(stdSigs) != len(tx.GetSigners()) {
if len(stdSigs) != len(stdTx.GetSigners()) {
return sdkerrors.Wrapf(
sdkerrors.ErrUnauthorized,
"wrong number of signers; expected %d, got %d", len(tx.GetSigners()), len(stdSigs),
"wrong number of signers; expected %d, got %d", len(stdTx.GetSigners()), len(stdSigs),
)
}

View File

@ -12,7 +12,6 @@ import (
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
"github.com/cosmos/cosmos-sdk/crypto/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -255,7 +254,7 @@ func TestSignatureV2Conversions(t *testing.T) {
pubKey, pubKey2,
})
dummy2 := []byte("dummySig2")
bitArray := types.NewCompactBitArray(2)
bitArray := cryptotypes.NewCompactBitArray(2)
bitArray.SetIndex(0, true)
bitArray.SetIndex(1, true)
msigData := &signing.MultiSignatureData{

View File

@ -17,7 +17,7 @@ type SigVerifiableTx interface {
}
// Tx defines a transaction interface that supports all standard message, signature
// fee, memo, and auxiliary interfaces.
// fee, memo, tips, and auxiliary interfaces.
type Tx interface {
SigVerifiableTx

View File

@ -36,6 +36,7 @@ type wrapper struct {
var (
_ authsigning.Tx = &wrapper{}
_ client.TxBuilder = &wrapper{}
_ tx.TipTx = &wrapper{}
_ middleware.HasExtensionOptionsTx = &wrapper{}
_ ExtensionOptionsTxBuilder = &wrapper{}
_ tx.TipTx = &wrapper{}

View File

@ -10,8 +10,8 @@ import (
// DefaultSignModes are the default sign modes enabled for protobuf transactions.
var DefaultSignModes = []signingtypes.SignMode{
signingtypes.SignMode_SIGN_MODE_DIRECT,
signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
signingtypes.SignMode_SIGN_MODE_DIRECT_AUX,
signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
}
// makeSignModeHandler returns the default protobuf SignModeHandler supporting