207 lines
6.4 KiB
Go
207 lines
6.4 KiB
Go
|
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)
|
||
|
}
|