Skip to content

Commit

Permalink
Tx Height Timeout (cosmos#6089)
Browse files Browse the repository at this point in the history
* Implement TxHeightTimeoutDecorator

* Update error message

* rename type

* set height timeout

* update tests

* update *SignDoc)

* rename

* remove dup

* fix build

* cli updates

* rest updates

* cl++

* Update x/auth/ante/basic.go

Co-authored-by: colin axnér <[email protected]>

* Update x/auth/ante/basic.go

Co-authored-by: colin axnér <[email protected]>

* Update x/auth/ante/basic.go

Co-authored-by: colin axnér <[email protected]>

* Update x/auth/ante/basic.go

Co-authored-by: Federico Kunze <[email protected]>

* rename

* rename

* remove TimeoutHeight from SignDoc

* updates

* fix tests

* update IBC cmd flags

* use omitempty

* update godoc

* add test case to TestStdSignBytes

Co-authored-by: colin axnér <[email protected]>
Co-authored-by: Federico Kunze <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 7, 2020
1 parent 6ebc476 commit 9ae1766
Show file tree
Hide file tree
Showing 34 changed files with 889 additions and 761 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa

### Features

* [\#6089](https://github.com/cosmos/cosmos-sdk/pull/6089) Transactions can now have a `TimeoutHeight` set which allows the transaction to be rejected if it's committed at a height greater than the timeout.
* (tests) [\#6489](https://github.com/cosmos/cosmos-sdk/pull/6489) Introduce package `testutil`, new in-process testing network framework for use in integration and unit tests.
* (crypto/multisig) [\#6241](https://github.com/cosmos/cosmos-sdk/pull/6241) Add Multisig type directly to the repo. Previously this was in tendermint.
* (rest) [\#6167](https://github.com/cosmos/cosmos-sdk/pull/6167) Support `max-body-bytes` CLI flag for the REST service.
Expand Down
2 changes: 2 additions & 0 deletions client/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
FlagPageKey = "page-key"
FlagOffset = "offset"
FlagCountTotal = "count-total"
FlagTimeoutHeight = "timeout-height"
)

// LineBreak can be included in a command list to provide a blank line
Expand Down Expand Up @@ -100,6 +101,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
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)")
cmd.Flags().String(FlagSignMode, "", "Choose sign mode (direct|amino-json), 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")

// --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
4 changes: 2 additions & 2 deletions client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func (s *TxConfigTestSuite) TestTxEncodeDecode() {
s.T().Log("decode transaction")
tx2, err := s.TxConfig.TxDecoder()(txBytes)
s.Require().NoError(err)
tx3, ok := tx2.(signing.SigFeeMemoTx)
tx3, ok := tx2.(signing.Tx)
s.Require().True(ok)
s.Require().Equal([]sdk.Msg{msg}, tx3.GetMsgs())
s.Require().Equal(feeAmount, tx3.GetFee())
Expand All @@ -276,7 +276,7 @@ func (s *TxConfigTestSuite) TestTxEncodeDecode() {
s.T().Log("JSON decode transaction")
tx2, err = s.TxConfig.TxJSONDecoder()(jsonTxBytes)
s.Require().NoError(err)
tx3, ok = tx2.(signing.SigFeeMemoTx)
tx3, ok = tx2.(signing.Tx)
s.Require().True(ok)
s.Require().Equal([]sdk.Msg{msg}, tx3.GetMsgs())
s.Require().Equal(feeAmount, tx3.GetFee())
Expand Down
10 changes: 10 additions & 0 deletions client/tx/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Factory struct {
accountNumber uint64
sequence uint64
gas uint64
timeoutHeight uint64
gasAdjustment float64
chainID string
memo string
Expand Down Expand Up @@ -48,6 +49,7 @@ func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory {
accSeq, _ := flagSet.GetUint64(flags.FlagSequence)
gasAdj, _ := flagSet.GetFloat64(flags.FlagGasAdjustment)
memo, _ := flagSet.GetString(flags.FlagMemo)
timeoutHeight, _ := flagSet.GetUint64(flags.FlagTimeoutHeight)

gasStr, _ := flagSet.GetString(flags.FlagGas)
gasSetting, _ := flags.ParseGasSetting(gasStr)
Expand All @@ -61,6 +63,7 @@ func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory {
simulateAndExecute: gasSetting.Simulate,
accountNumber: accNum,
sequence: accSeq,
timeoutHeight: timeoutHeight,
gasAdjustment: gasAdj,
memo: memo,
signMode: signMode,
Expand All @@ -85,6 +88,7 @@ func (f Factory) Memo() string { return f.memo }
func (f Factory) Fees() sdk.Coins { return f.fees }
func (f Factory) GasPrices() sdk.DecCoins { return f.gasPrices }
func (f Factory) AccountRetriever() client.AccountRetriever { return f.accountRetriever }
func (f Factory) TimeoutHeight() uint64 { return f.timeoutHeight }

// SimulateAndExecute returns the option to simulate and then execute the transaction
// using the gas from the simulation results
Expand Down Expand Up @@ -183,3 +187,9 @@ func (f Factory) WithSignMode(mode signing.SignMode) Factory {
f.signMode = mode
return f
}

// WithTimeoutHeight returns a copy of the Factory with an updated timeout height.
func (f Factory) WithTimeoutHeight(height uint64) Factory {
f.timeoutHeight = height
return f
}
6 changes: 3 additions & 3 deletions client/tx/legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// ConvertTxToStdTx converts a transaction to the legacy StdTx format
func ConvertTxToStdTx(codec *codec.Codec, tx signing.SigFeeMemoTx) (types.StdTx, error) {
func ConvertTxToStdTx(codec *codec.Codec, tx signing.Tx) (types.StdTx, error) {
if stdTx, ok := tx.(types.StdTx); ok {
return stdTx, nil
}
Expand All @@ -33,9 +33,9 @@ func ConvertTxToStdTx(codec *codec.Codec, tx signing.SigFeeMemoTx) (types.StdTx,
return stdTx, nil
}

// CopyTx copies a SigFeeMemoTx to a new TxBuilder, allowing conversion between
// CopyTx copies a Tx to a new TxBuilder, allowing conversion between
// different transaction formats.
func CopyTx(tx signing.SigFeeMemoTx, builder client.TxBuilder) error {
func CopyTx(tx signing.Tx, builder client.TxBuilder) error {
err := builder.SetMsgs(tx.GetMsgs()...)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion client/tx/legacy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (s *TestSuite) TestConvertAndEncodeStdTx() {
decodedTx, err := s.protoCfg.TxDecoder()(txBz)
s.Require().NoError(err)
aminoBuilder2 := s.aminoCfg.NewTxBuilder()
s.Require().NoError(tx2.CopyTx(decodedTx.(signing.SigFeeMemoTx), aminoBuilder2))
s.Require().NoError(tx2.CopyTx(decodedTx.(signing.Tx), aminoBuilder2))
s.Require().Equal(stdTx, aminoBuilder2.GetTx())

// just use amino everywhere
Expand Down
4 changes: 3 additions & 1 deletion client/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ func WriteGeneratedTxResponse(
WithMemo(br.Memo).
WithChainID(br.ChainID).
WithSimulateAndExecute(br.Simulate).
WithTxConfig(ctx.TxConfig)
WithTxConfig(ctx.TxConfig).
WithTimeoutHeight(br.TimeoutHeight)

if br.Simulate || gasSetting.Simulate {
if gasAdj < 0 {
Expand Down Expand Up @@ -238,6 +239,7 @@ func BuildUnsignedTx(txf Factory, msgs ...sdk.Msg) (client.TxBuilder, error) {
tx.SetMemo(txf.memo)
tx.SetFeeAmount(fees)
tx.SetGasLimit(txf.gas)
tx.SetTimeoutHeight(txf.TimeoutHeight())

return tx, nil
}
Expand Down
3 changes: 2 additions & 1 deletion client/tx_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ type (
// signatures, and provide canonical bytes to sign over. The transaction must
// also know how to encode itself.
TxBuilder interface {
GetTx() signing.SigFeeMemoTx
GetTx() signing.Tx

SetMsgs(msgs ...sdk.Msg) error
SetSignatures(signatures ...signingtypes.SignatureV2) error
SetMemo(memo string)
SetFeeAmount(amount sdk.Coins)
SetGasLimit(limit uint64)
SetTimeoutHeight(height uint64)
}
)
2 changes: 1 addition & 1 deletion proto/cosmos/tx/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ message TxBody {

// timeout is the block height after which this transaction will not
// be processed by the chain
int64 timeout_height = 3;
uint64 timeout_height = 3;

// extension_options are arbitrary options that can be added by chains
// when the default options are not sufficient. If any of these are present
Expand Down
4 changes: 2 additions & 2 deletions simapp/params/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ func MakeEncodingConfig() EncodingConfig {
amino := codec.New()
interfaceRegistry := types.NewInterfaceRegistry()
marshaler := codec.NewHybridCodec(amino, interfaceRegistry)
txGen := tx.NewTxConfig(codec.NewProtoCodec(interfaceRegistry), std.DefaultPublicKeyCodec{}, tx.DefaultSignModes)
txCfg := tx.NewTxConfig(codec.NewProtoCodec(interfaceRegistry), std.DefaultPublicKeyCodec{}, tx.DefaultSignModes)

return EncodingConfig{
InterfaceRegistry: interfaceRegistry,
Marshaler: marshaler,
TxConfig: txGen,
TxConfig: txCfg,
Amino: amino,
}
}
4 changes: 4 additions & 0 deletions types/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ var (
// ErrInvalidType defines an error an invalid type.
ErrInvalidType = Register(RootCodespace, 29, "invalid type")

// ErrTxTimeoutHeight defines an error for when a tx is rejected out due to an
// explicitly set timeout height.
ErrTxTimeoutHeight = Register(RootCodespace, 30, "tx timeout height")

// ErrPanic is only set when we recover from a panic, so we know to
// redact potentially sensitive system info
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
Expand Down
1 change: 1 addition & 0 deletions types/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type BaseReq struct {
ChainID string `json:"chain_id"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
TimeoutHeight uint64 `json:"timeout_height"`
Fees sdk.Coins `json:"fees"`
GasPrices sdk.DecCoins `json:"gas_prices"`
Gas string `json:"gas"`
Expand Down
56 changes: 28 additions & 28 deletions types/tx/tx.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions types/tx_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,18 @@ type (
Tx
GetMemo() string
}

// TxWithTimeoutHeight extends the Tx interface by allowing a transaction to
// set a height timeout.
TxWithTimeoutHeight interface {
Tx

GetTimeoutHeight() uint64
}
)

// TxDecoder unmarshals transaction bytes
type TxDecoder func(txBytes []byte) (Tx, error)

// TxEncoder marshals transaction to bytes
type TxEncoder func(tx Tx) ([]byte, error)

//__________________________________________________________
1 change: 1 addition & 0 deletions x/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func NewAnteHandler(
NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
NewMempoolFeeDecorator(),
NewValidateBasicDecorator(),
TxTimeoutHeightDecorator{},
NewValidateMemoDecorator(ak),
NewConsumeGasForTxSizeDecorator(ak),
NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators
Expand Down
37 changes: 35 additions & 2 deletions x/auth/ante/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package ante
import (
"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/cosmos-sdk/x/auth/signing"

"github.com/cosmos/cosmos-sdk/codec/legacy"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

Expand Down Expand Up @@ -141,3 +140,37 @@ func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, sim

return next(ctx, tx, simulate)
}

type (
// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
// tx height timeout.
TxTimeoutHeightDecorator struct{}

// TxWithTimeoutHeight defines the interface a tx must implement in order for
// TxHeightTimeoutDecorator to process the tx.
TxWithTimeoutHeight interface {
sdk.Tx

GetTimeoutHeight() uint64
}
)

// AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator
// type where the current block height is checked against the tx's height timeout.
// If a height timeout is provided (non-zero) and is less than the current block
// height, then an error is returned.
func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
timeoutTx, ok := tx.(TxWithTimeoutHeight)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight")
}

timeoutHeight := timeoutTx.GetTimeoutHeight()
if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight,
)
}

return next(ctx, tx, simulate)
}
Loading

0 comments on commit 9ae1766

Please sign in to comment.