package ante import ( "github.com/cosmos/cosmos-sdk/codec/legacy" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) // ValidateBasicDecorator will call tx.ValidateBasic and return any non-nil error. // If ValidateBasic passes, decorator calls next AnteHandler in chain. Note, // ValidateBasicDecorator decorator will not get executed on ReCheckTx since it // is not dependent on application state. type ValidateBasicDecorator struct{} func NewValidateBasicDecorator() ValidateBasicDecorator { return ValidateBasicDecorator{} } func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { // no need to validate basic on recheck tx, call next antehandler if ctx.IsReCheckTx() { return next(ctx, tx, simulate) } if err := tx.ValidateBasic(); err != nil { return ctx, err } return next(ctx, tx, simulate) } // ValidateMemoDecorator will validate memo given the parameters passed in // If memo is too large decorator returns with error, otherwise call next AnteHandler // CONTRACT: Tx must implement TxWithMemo interface type ValidateMemoDecorator struct { ak AccountKeeper } func NewValidateMemoDecorator(ak AccountKeeper) ValidateMemoDecorator { return ValidateMemoDecorator{ ak: ak, } } func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { memoTx, ok := tx.(sdk.TxWithMemo) if !ok { return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type") } params := vmd.ak.GetParams(ctx) memoLength := len(memoTx.GetMemo()) if uint64(memoLength) > params.MaxMemoCharacters { return ctx, sdkerrors.Wrapf(sdkerrors.ErrMemoTooLarge, "maximum number of characters is %d but received %d characters", params.MaxMemoCharacters, memoLength, ) } return next(ctx, tx, simulate) } // ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional // to the size of tx before calling next AnteHandler. Note, the gas costs will be // slightly over estimated due to the fact that any given signing account may need // to be retrieved from state. // // CONTRACT: If simulate=true, then signatures must either be completely filled // in or empty. // CONTRACT: To use this decorator, signatures of transaction must be represented // as legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost. type ConsumeTxSizeGasDecorator struct { ak AccountKeeper } func NewConsumeGasForTxSizeDecorator(ak AccountKeeper) ConsumeTxSizeGasDecorator { return ConsumeTxSizeGasDecorator{ ak: ak, } } func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") } params := cgts.ak.GetParams(ctx) ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize") // simulate gas cost for signatures in simulate mode if simulate { // in simulate mode, each element should be a nil signature sigs, err := sigTx.GetSignaturesV2() if err != nil { return ctx, err } n := len(sigs) for i, signer := range sigTx.GetSigners() { // if signature is already filled in, no need to simulate gas cost if i < n && !isIncompleteSignature(sigs[i].Data) { continue } var pubkey cryptotypes.PubKey acc := cgts.ak.GetAccount(ctx, signer) // use placeholder simSecp256k1Pubkey if sig is nil if acc == nil || acc.GetPubKey() == nil { pubkey = simSecp256k1Pubkey } else { pubkey = acc.GetPubKey() } // use stdsignature to mock the size of a full signature simSig := legacytx.StdSignature{ //nolint:staticcheck // this will be removed when proto is ready Signature: simSecp256k1Sig[:], PubKey: pubkey, } sigBz := legacy.Cdc.MustMarshal(simSig) cost := sdk.Gas(len(sigBz) + 6) // If the pubkey is a multi-signature pubkey, then we estimate for the maximum // number of signers. if _, ok := pubkey.(*multisig.LegacyAminoPubKey); ok { cost *= params.TxSigLimit } ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") } } return next(ctx, tx, simulate) } // isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes func isIncompleteSignature(data signing.SignatureData) bool { if data == nil { return true } switch data := data.(type) { case *signing.SingleSignatureData: return len(data.Signature) == 0 case *signing.MultiSignatureData: if len(data.Signatures) == 0 { return true } for _, s := range data.Signatures { if isIncompleteSignature(s) { return true } } } return false } 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 } ) // TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a // tx height timeout. func NewTxTimeoutHeightDecorator() TxTimeoutHeightDecorator { return TxTimeoutHeightDecorator{} } // 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) }