Co-authored-by: Marko <marko@baricevic.me> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
280 lines
8.6 KiB
Go
280 lines
8.6 KiB
Go
package ante
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"cosmossdk.io/core/appmodule/v2"
|
|
"cosmossdk.io/core/transaction"
|
|
errorsmod "cosmossdk.io/errors"
|
|
storetypes "cosmossdk.io/store/types"
|
|
"cosmossdk.io/x/auth/migrations/legacytx"
|
|
authsigning "cosmossdk.io/x/auth/signing"
|
|
|
|
"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"
|
|
)
|
|
|
|
// 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 {
|
|
env appmodule.Environment
|
|
}
|
|
|
|
func NewValidateBasicDecorator(env appmodule.Environment) ValidateBasicDecorator {
|
|
return ValidateBasicDecorator{
|
|
env: env,
|
|
}
|
|
}
|
|
|
|
// AnteHandle implements an AnteHandler decorator for the ValidateBasicDecorator.
|
|
func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
|
if err := vbd.ValidateTx(ctx, tx); err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
return next(ctx, tx, false)
|
|
}
|
|
|
|
// ValidateTx implements an TxValidator for ValidateBasicDecorator
|
|
func (vbd ValidateBasicDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
|
|
// no need to validate basic on recheck tx, call next antehandler
|
|
txService := vbd.env.TransactionService
|
|
if txService.ExecMode(ctx) == transaction.ExecModeReCheck {
|
|
return nil
|
|
}
|
|
|
|
if validateBasic, ok := tx.(sdk.HasValidateBasic); ok {
|
|
if err := validateBasic.ValidateBasic(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
// AnteHandle implements an AnteHandler decorator for the ValidateMemoDecorator.
|
|
func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
|
if err := vmd.ValidateTx(ctx, tx); err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
return next(ctx, tx, false)
|
|
}
|
|
|
|
// ValidateTx implements an TxValidator for ValidateMemoDecorator
|
|
func (vmd ValidateMemoDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
|
|
memoTx, ok := tx.(sdk.TxWithMemo)
|
|
if !ok {
|
|
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
|
}
|
|
|
|
memoLength := len(memoTx.GetMemo())
|
|
if memoLength > 0 {
|
|
params := vmd.ak.GetParams(ctx)
|
|
if uint64(memoLength) > params.MaxMemoCharacters {
|
|
return errorsmod.Wrapf(sdkerrors.ErrMemoTooLarge,
|
|
"maximum number of characters is %d but received %d characters",
|
|
params.MaxMemoCharacters, memoLength,
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 exec mode = simulate, 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,
|
|
}
|
|
}
|
|
|
|
// AnteHandle implements an AnteHandler decorator for the ConsumeTxSizeGasDecorator.
|
|
func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
|
if err := cgts.ValidateTx(ctx, tx); err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
return next(ctx, tx, false)
|
|
}
|
|
|
|
// ValidateTx implements an TxValidator for ConsumeTxSizeGasDecorator
|
|
func (cgts ConsumeTxSizeGasDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
|
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
|
if !ok {
|
|
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
|
|
}
|
|
params := cgts.ak.GetParams(ctx)
|
|
|
|
gasService := cgts.ak.GetEnvironment().GasService
|
|
if err := gasService.GasMeter(ctx).Consume(params.TxSizeCostPerByte*storetypes.Gas(len(tx.Bytes())), "txSize"); err != nil {
|
|
return err
|
|
}
|
|
|
|
// simulate gas cost for signatures in simulate mode
|
|
txService := cgts.ak.GetEnvironment().TransactionService
|
|
if txService.ExecMode(ctx) == transaction.ExecModeSimulate {
|
|
// in simulate mode, each element should be a nil signature
|
|
sigs, err := sigTx.GetSignaturesV2()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n := len(sigs)
|
|
|
|
for i, signer := range sigs {
|
|
// if signature is already filled in, no need to simulate gas cost
|
|
if i < n && !isIncompleteSignature(sigs[i].Data) {
|
|
continue
|
|
}
|
|
|
|
var pubkey cryptotypes.PubKey
|
|
|
|
// use placeholder simSecp256k1Pubkey if sig is nil
|
|
if signer.PubKey == nil {
|
|
pubkey = simSecp256k1Pubkey
|
|
} else {
|
|
pubkey = signer.PubKey
|
|
}
|
|
|
|
// use stdsignature to mock the size of a full signature
|
|
simSig := legacytx.StdSignature{ //nolint:staticcheck // SA1019: legacytx.StdSignature is deprecated
|
|
Signature: simSecp256k1Sig[:],
|
|
PubKey: pubkey,
|
|
}
|
|
|
|
sigBz := legacy.Cdc.MustMarshal(simSig)
|
|
cost := storetypes.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
|
|
}
|
|
|
|
if err := gasService.GasMeter(ctx).Consume(params.TxSizeCostPerByte*cost, "txSize"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
env appmodule.Environment
|
|
}
|
|
|
|
// TxWithTimeoutHeight defines the interface a tx must implement in order for
|
|
// TxTimeoutHeightDecorator to process the tx.
|
|
TxWithTimeoutHeight interface {
|
|
sdk.Tx
|
|
|
|
GetTimeoutHeight() uint64
|
|
GetTimeoutTimeStamp() time.Time
|
|
}
|
|
)
|
|
|
|
// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
|
|
// tx height timeout.
|
|
func NewTxTimeoutHeightDecorator(env appmodule.Environment) TxTimeoutHeightDecorator {
|
|
return TxTimeoutHeightDecorator{
|
|
env: env,
|
|
}
|
|
}
|
|
|
|
// AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator.
|
|
func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
|
if err := txh.ValidateTx(ctx, tx); err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
return next(ctx, tx, false)
|
|
}
|
|
|
|
// ValidateTx implements an TxValidator 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) ValidateTx(ctx context.Context, tx sdk.Tx) error {
|
|
timeoutTx, ok := tx.(TxWithTimeoutHeight)
|
|
if !ok {
|
|
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight")
|
|
}
|
|
|
|
timeoutHeight := timeoutTx.GetTimeoutHeight()
|
|
headerInfo := txh.env.HeaderService.HeaderInfo(ctx)
|
|
|
|
if timeoutHeight > 0 && uint64(headerInfo.Height) > timeoutHeight {
|
|
return errorsmod.Wrapf(
|
|
sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", headerInfo.Height, timeoutHeight,
|
|
)
|
|
}
|
|
|
|
timeoutTimestamp := timeoutTx.GetTimeoutTimeStamp()
|
|
if !timeoutTimestamp.IsZero() && timeoutTimestamp.Unix() != 0 && timeoutTimestamp.Before(headerInfo.Time) {
|
|
return errorsmod.Wrapf(
|
|
sdkerrors.ErrTxTimeout, "block time: %s, timeout timestamp: %s", headerInfo.Time.String(), timeoutTimestamp.String(),
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|