Synopsis

This document describes the default strategies to handle gas and fees within a Cosmos SDK application.

Introduction to Gas and Fees

In the Cosmos SDK, gas is a special unit that is used to track the consumption of resources during execution. gas is typically consumed whenever read and writes are made to the store, but it can also be consumed if expensive computation needs to be done. It serves two main purposes:
  • Make sure blocks are not consuming too many resources and are finalized. This is implemented by default in the Cosmos SDK via the block gas meter.
  • Prevent spam and abuse from end-user. To this end, gas consumed during message execution is typically priced, resulting in a fee (fees = gas * gas-prices). fees generally have to be paid by the sender of the message. Note that the Cosmos SDK does not enforce gas pricing by default, as there may be other ways to prevent spam (e.g. bandwidth schemes). Still, most applications implement fee mechanisms to prevent spam by using the AnteHandler.

Gas Meter

In the Cosmos SDK, gas is a simple alias for uint64, and is managed by an object called a gas meter. Gas meters implement the GasMeter interface
package types

import (

	"fmt"
    "math"
)

/ Gas consumption descriptors.
const (
	GasIterNextCostFlatDesc = "IterNextFlat"
	GasValuePerByteDesc     = "ValuePerByte"
	GasWritePerByteDesc     = "WritePerByte"
	GasReadPerByteDesc      = "ReadPerByte"
	GasWriteCostFlatDesc    = "WriteFlat"
	GasReadCostFlatDesc     = "ReadFlat"
	GasHasDesc              = "Has"
	GasDeleteDesc           = "Delete"
)

/ Gas measured by the SDK
type Gas = uint64

/ ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a
/ negative gas consumed amount.
type ErrorNegativeGasConsumed struct {
    Descriptor string
}

/ ErrorOutOfGas defines an error thrown when an action results in out of gas.
type ErrorOutOfGas struct {
    Descriptor string
}

/ ErrorGasOverflow defines an error thrown when an action results gas consumption
/ unsigned integer overflow.
type ErrorGasOverflow struct {
    Descriptor string
}

/ GasMeter interface to track gas consumption
type GasMeter interface {
    GasConsumed()

Gas
	GasConsumedToLimit()

Gas
	GasRemaining()

Gas
	Limit()

Gas
	ConsumeGas(amount Gas, descriptor string)

RefundGas(amount Gas, descriptor string)

IsPastLimit()

bool
	IsOutOfGas()

bool
	String()

string
}

type basicGasMeter struct {
    limit    Gas
	consumed Gas
}

/ NewGasMeter returns a reference to a new basicGasMeter.
func NewGasMeter(limit Gas)

GasMeter {
    return &basicGasMeter{
    limit:    limit,
		consumed: 0,
}
}

/ GasConsumed returns the gas consumed from the GasMeter.
func (g *basicGasMeter)

GasConsumed()

Gas {
    return g.consumed
}

/ GasRemaining returns the gas left in the GasMeter.
func (g *basicGasMeter)

GasRemaining()

Gas {
    if g.IsPastLimit() {
    return 0
}

return g.limit - g.consumed
}

/ Limit returns the gas limit of the GasMeter.
func (g *basicGasMeter)

Limit()

Gas {
    return g.limit
}

/ GasConsumedToLimit returns the gas limit if gas consumed is past the limit,
/ otherwise it returns the consumed gas.
/ NOTE: This behaviour is only called when recovering from panic when
/ BlockGasMeter consumes gas past the limit.
func (g *basicGasMeter)

GasConsumedToLimit()

Gas {
    if g.IsPastLimit() {
    return g.limit
}

return g.consumed
}

/ addUint64Overflow performs the addition operation on two uint64 integers and
/ returns a boolean on whether or not the result overflows.
func addUint64Overflow(a, b uint64) (uint64, bool) {
    if math.MaxUint64-a < b {
    return 0, true
}

return a + b, false
}

/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit or out of gas.
func (g *basicGasMeter)

ConsumeGas(amount Gas, descriptor string) {
    var overflow bool
	g.consumed, overflow = addUint64Overflow(g.consumed, amount)
    if overflow {
    g.consumed = math.MaxUint64
		panic(ErrorGasOverflow{
    descriptor
})
}
    if g.consumed > g.limit {
    panic(ErrorOutOfGas{
    descriptor
})
}
}

/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the transaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *basicGasMeter)

RefundGas(amount Gas, descriptor string) {
    if g.consumed < amount {
    panic(ErrorNegativeGasConsumed{
    Descriptor: descriptor
})
}

g.consumed -= amount
}

/ IsPastLimit returns true if gas consumed is past limit, otherwise it returns false.
func (g *basicGasMeter)

IsPastLimit()

bool {
    return g.consumed > g.limit
}

/ IsOutOfGas returns true if gas consumed is greater than or equal to gas limit, otherwise it returns false.
func (g *basicGasMeter)

IsOutOfGas()

bool {
    return g.consumed >= g.limit
}

/ String returns the BasicGasMeter's gas limit and gas consumed.
func (g *basicGasMeter)

String()

string {
    return fmt.Sprintf("BasicGasMeter:\n  limit: %d\n  consumed: %d", g.limit, g.consumed)
}

type infiniteGasMeter struct {
    consumed Gas
}

/ NewInfiniteGasMeter returns a new gas meter without a limit.
func NewInfiniteGasMeter()

GasMeter {
    return &infiniteGasMeter{
    consumed: 0,
}
}

/ GasConsumed returns the gas consumed from the GasMeter.
func (g *infiniteGasMeter)

GasConsumed()

Gas {
    return g.consumed
}

/ GasConsumedToLimit returns the gas consumed from the GasMeter since the gas is not confined to a limit.
/ NOTE: This behaviour is only called when recovering from panic when BlockGasMeter consumes gas past the limit.
func (g *infiniteGasMeter)

GasConsumedToLimit()

Gas {
    return g.consumed
}

/ GasRemaining returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)

GasRemaining()

Gas {
    return math.MaxUint64
}

/ Limit returns MaxUint64 since limit is not confined in infiniteGasMeter.
func (g *infiniteGasMeter)

Limit()

Gas {
    return math.MaxUint64
}

/ ConsumeGas adds the given amount of gas to the gas consumed and panics if it overflows the limit.
func (g *infiniteGasMeter)

ConsumeGas(amount Gas, descriptor string) {
    var overflow bool
	/ TODO: Should we set the consumed field after overflow checking?
	g.consumed, overflow = addUint64Overflow(g.consumed, amount)
    if overflow {
    panic(ErrorGasOverflow{
    descriptor
})
}
}

/ RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the
/ gas consumed, the function will panic.
/
/ Use case: This functionality enables refunding gas to the trasaction or block gas pools so that
/ EVM-compatible chains can fully support the go-ethereum StateDb interface.
/ See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference.
func (g *infiniteGasMeter)

RefundGas(amount Gas, descriptor string) {
    if g.consumed < amount {
    panic(ErrorNegativeGasConsumed{
    Descriptor: descriptor
})
}

g.consumed -= amount
}

/ IsPastLimit returns false since the gas limit is not confined.
func (g *infiniteGasMeter)

IsPastLimit()

bool {
    return false
}

/ IsOutOfGas returns false since the gas limit is not confined.
func (g *infiniteGasMeter)

IsOutOfGas()

bool {
    return false
}

/ String returns the InfiniteGasMeter's gas consumed.
func (g *infiniteGasMeter)

String()

string {
    return fmt.Sprintf("InfiniteGasMeter:\n  consumed: %d", g.consumed)
}

/ GasConfig defines gas cost for each operation on KVStores
type GasConfig struct {
    HasCost          Gas
	DeleteCost       Gas
	ReadCostFlat     Gas
	ReadCostPerByte  Gas
	WriteCostFlat    Gas
	WriteCostPerByte Gas
	IterNextCostFlat Gas
}

/ KVGasConfig returns a default gas config for KVStores.
func KVGasConfig()

GasConfig {
    return GasConfig{
    HasCost:          1000,
    DeleteCost:       1000,
    ReadCostFlat:     1000,
    ReadCostPerByte:  3,
    WriteCostFlat:    2000,
    WriteCostPerByte: 30,
    IterNextCostFlat: 30,
}
}

/ TransientGasConfig returns a default gas config for TransientStores.
func TransientGasConfig()

GasConfig {
    return GasConfig{
    HasCost:          100,
    DeleteCost:       100,
    ReadCostFlat:     100,
    ReadCostPerByte:  0,
    WriteCostFlat:    200,
    WriteCostPerByte: 3,
    IterNextCostFlat: 3,
}
}
where:
  • GasConsumed() returns the amount of gas that was consumed by the gas meter instance.
  • GasConsumedToLimit() returns the amount of gas that was consumed by gas meter instance, or the limit if it is reached.
  • GasRemaining() returns the gas left in the GasMeter.
  • Limit() returns the limit of the gas meter instance. 0 if the gas meter is infinite.
  • ConsumeGas(amount Gas, descriptor string) consumes the amount of gas provided. If the gas overflows, it panics with the descriptor message. If the gas meter is not infinite, it panics if gas consumed goes above the limit.
  • RefundGas() deducts the given amount from the gas consumed. This functionality enables refunding gas to the transaction or block gas pools so that EVM-compatible chains can fully support the go-ethereum StateDB interface.
  • IsPastLimit() returns true if the amount of gas consumed by the gas meter instance is strictly above the limit, false otherwise.
  • IsOutOfGas() returns true if the amount of gas consumed by the gas meter instance is above or equal to the limit, false otherwise.
The gas meter is generally held in ctx, and consuming gas is done with the following pattern:
ctx.GasMeter().ConsumeGas(amount, "description")
By default, the Cosmos SDK makes use of two different gas meters, the main gas meter and the block gas meter.

Main Gas Meter

ctx.GasMeter() is the main gas meter of the application. The main gas meter is initialized in BeginBlock via setDeliverState, and then tracks gas consumption during execution sequences that lead to state-transitions, i.e. those originally triggered by BeginBlock, DeliverTx and EndBlock. At the beginning of each DeliverTx, the main gas meter must be set to 0 in the AnteHandler, so that it can track gas consumption per-transaction. Gas consumption can be done manually, generally by the module developer in the BeginBlocker, EndBlocker or Msg service, but most of the time it is done automatically whenever there is a read or write to the store. This automatic gas consumption logic is implemented in a special store called GasKv.

Block Gas Meter

ctx.BlockGasMeter() is the gas meter used to track gas consumption per block and make sure it does not go above a certain limit. A new instance of the BlockGasMeter is created each time BeginBlock is called. The BlockGasMeter is finite, and the limit of gas per block is defined in the application’s consensus parameters. By default, Cosmos SDK applications use the default consensus parameters provided by CometBFT:
package types

import (

	"errors"
    "fmt"
    "time"
    "github.com/cometbft/cometbft/crypto/ed25519"
    "github.com/cometbft/cometbft/crypto/secp256k1"
    "github.com/cometbft/cometbft/crypto/tmhash"
	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
)

const (
	/ MaxBlockSizeBytes is the maximum permitted size of the blocks.
	MaxBlockSizeBytes = 104857600 / 100MB

	/ BlockPartSizeBytes is the size of one block part.
	BlockPartSizeBytes uint32 = 65536 / 64kB

	/ MaxBlockPartsCount is the maximum number of block parts.
	MaxBlockPartsCount = (MaxBlockSizeBytes / BlockPartSizeBytes) + 1

	ABCIPubKeyTypeEd25519   = ed25519.KeyType
	ABCIPubKeyTypeSecp256k1 = secp256k1.KeyType
)

var ABCIPubKeyTypesToNames = map[string]string{
    ABCIPubKeyTypeEd25519:   ed25519.PubKeyName,
	ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyName,
}

/ ConsensusParams contains consensus critical parameters that determine the
/ validity of blocks.
type ConsensusParams struct {
    Block     BlockParams     `json:"block"`
	Evidence  EvidenceParams  `json:"evidence"`
	Validator ValidatorParams `json:"validator"`
	Version   VersionParams   `json:"version"`
}

/ BlockParams define limits on the block size and gas plus minimum time
/ between blocks.
type BlockParams struct {
    MaxBytes int64 `json:"max_bytes"`
	MaxGas   int64 `json:"max_gas"`
}

/ EvidenceParams determine how we handle evidence of malfeasance.
type EvidenceParams struct {
    MaxAgeNumBlocks int64         `json:"max_age_num_blocks"` / only accept new evidence more recent than this
	MaxAgeDuration  time.Duration `json:"max_age_duration"`
	MaxBytes        int64         `json:"max_bytes"`
}

/ ValidatorParams restrict the public key types validators can use.
/ NOTE: uses ABCI pubkey naming, not Amino names.
type ValidatorParams struct {
    PubKeyTypes []string `json:"pub_key_types"`
}

type VersionParams struct {
    App uint64 `json:"app"`
}

/ DefaultConsensusParams returns a default ConsensusParams.
func DefaultConsensusParams() *ConsensusParams {
    return &ConsensusParams{
    Block:     DefaultBlockParams(),
    Evidence:  DefaultEvidenceParams(),
    Validator: DefaultValidatorParams(),
    Version:   DefaultVersionParams(),
}
}

/ DefaultBlockParams returns a default BlockParams.
func DefaultBlockParams()

BlockParams {
    return BlockParams{
    MaxBytes: 22020096, / 21MB
		MaxGas:   -1,
}
}

/ DefaultEvidenceParams returns a default EvidenceParams.
func DefaultEvidenceParams()

EvidenceParams {
    return EvidenceParams{
    MaxAgeNumBlocks: 100000, / 27.8 hrs at 1block/s
		MaxAgeDuration:  48 * time.Hour,
    MaxBytes:        1048576, / 1MB
}
}

/ DefaultValidatorParams returns a default ValidatorParams, which allows
/ only ed25519 pubkeys.
func DefaultValidatorParams()

ValidatorParams {
    return ValidatorParams{
    PubKeyTypes: []string{
    ABCIPubKeyTypeEd25519
},
}
}

func DefaultVersionParams()

VersionParams {
    return VersionParams{
    App: 0,
}
}

func IsValidPubkeyType(params ValidatorParams, pubkeyType string)

bool {
    for i := 0; i < len(params.PubKeyTypes); i++ {
    if params.PubKeyTypes[i] == pubkeyType {
    return true
}

}

return false
}

/ Validate validates the ConsensusParams to ensure all values are within their
/ allowed limits, and returns an error if they are not.
func (params ConsensusParams)

ValidateBasic()

error {
    if params.Block.MaxBytes <= 0 {
    return fmt.Errorf("block.MaxBytes must be greater than 0. Got %d",
			params.Block.MaxBytes)
}
    if params.Block.MaxBytes > MaxBlockSizeBytes {
    return fmt.Errorf("block.MaxBytes is too big. %d > %d",
			params.Block.MaxBytes, MaxBlockSizeBytes)
}
    if params.Block.MaxGas < -1 {
    return fmt.Errorf("block.MaxGas must be greater or equal to -1. Got %d",
			params.Block.MaxGas)
}
    if params.Evidence.MaxAgeNumBlocks <= 0 {
    return fmt.Errorf("evidence.MaxAgeNumBlocks must be greater than 0. Got %d",
			params.Evidence.MaxAgeNumBlocks)
}
    if params.Evidence.MaxAgeDuration <= 0 {
    return fmt.Errorf("evidence.MaxAgeDuration must be grater than 0 if provided, Got %v",
			params.Evidence.MaxAgeDuration)
}
    if params.Evidence.MaxBytes > params.Block.MaxBytes {
    return fmt.Errorf("evidence.MaxBytesEvidence is greater than upper bound, %d > %d",
			params.Evidence.MaxBytes, params.Block.MaxBytes)
}
    if params.Evidence.MaxBytes < 0 {
    return fmt.Errorf("evidence.MaxBytes must be non negative. Got: %d",
			params.Evidence.MaxBytes)
}
    if len(params.Validator.PubKeyTypes) == 0 {
    return errors.New("len(Validator.PubKeyTypes)

must be greater than 0")
}

	/ Check if keyType is a known ABCIPubKeyType
    for i := 0; i < len(params.Validator.PubKeyTypes); i++ {
    keyType := params.Validator.PubKeyTypes[i]
    if _, ok := ABCIPubKeyTypesToNames[keyType]; !ok {
    return fmt.Errorf("params.Validator.PubKeyTypes[%d], %s, is an unknown pubkey type",
				i, keyType)
}

}

return nil
}

/ Hash returns a hash of a subset of the parameters to store in the block header.
/ Only the Block.MaxBytes and Block.MaxGas are included in the hash.
/ This allows the ConsensusParams to evolve more without breaking the block
/ protocol. No need for a Merkle tree here, just a small struct to hash.
func (params ConsensusParams)

Hash() []byte {
    hasher := tmhash.New()
    hp := cmtproto.HashedParams{
    BlockMaxBytes: params.Block.MaxBytes,
    BlockMaxGas:   params.Block.MaxGas,
}

bz, err := hp.Marshal()
    if err != nil {
    panic(err)
}

	_, err = hasher.Write(bz)
    if err != nil {
    panic(err)
}

return hasher.Sum(nil)
}

/ Update returns a copy of the params with updates from the non-zero fields of p2.
/ NOTE: note: must not modify the original
func (params ConsensusParams)

Update(params2 *cmtproto.ConsensusParams)

ConsensusParams {
    res := params / explicit copy
    if params2 == nil {
    return res
}

	/ we must defensively consider any structs may be nil
    if params2.Block != nil {
    res.Block.MaxBytes = params2.Block.MaxBytes
		res.Block.MaxGas = params2.Block.MaxGas
}
    if params2.Evidence != nil {
    res.Evidence.MaxAgeNumBlocks = params2.Evidence.MaxAgeNumBlocks
		res.Evidence.MaxAgeDuration = params2.Evidence.MaxAgeDuration
		res.Evidence.MaxBytes = params2.Evidence.MaxBytes
}
    if params2.Validator != nil {
		/ Copy params2.Validator.PubkeyTypes, and set result's value to the copy.
		/ This avoids having to initialize the slice to 0 values, and then write to it again.
		res.Validator.PubKeyTypes = append([]string{
}, params2.Validator.PubKeyTypes...)
}
    if params2.Version != nil {
    res.Version.App = params2.Version.App
}

return res
}

func (params *ConsensusParams)

ToProto()

cmtproto.ConsensusParams {
    return cmtproto.ConsensusParams{
    Block: &cmtproto.BlockParams{
    MaxBytes: params.Block.MaxBytes,
    MaxGas:   params.Block.MaxGas,
},
    Evidence: &cmtproto.EvidenceParams{
    MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
    MaxAgeDuration:  params.Evidence.MaxAgeDuration,
    MaxBytes:        params.Evidence.MaxBytes,
},
    Validator: &cmtproto.ValidatorParams{
    PubKeyTypes: params.Validator.PubKeyTypes,
},
    Version: &cmtproto.VersionParams{
    App: params.Version.App,
},
}
}

func ConsensusParamsFromProto(pbParams cmtproto.ConsensusParams)

ConsensusParams {
    return ConsensusParams{
    Block: BlockParams{
    MaxBytes: pbParams.Block.MaxBytes,
    MaxGas:   pbParams.Block.MaxGas,
},
    Evidence: EvidenceParams{
    MaxAgeNumBlocks: pbParams.Evidence.MaxAgeNumBlocks,
    MaxAgeDuration:  pbParams.Evidence.MaxAgeDuration,
    MaxBytes:        pbParams.Evidence.MaxBytes,
},
    Validator: ValidatorParams{
    PubKeyTypes: pbParams.Validator.PubKeyTypes,
},
    Version: VersionParams{
    App: pbParams.Version.App,
},
}
}
When a new transaction is being processed via DeliverTx, the current value of BlockGasMeter is checked to see if it is above the limit. If it is, DeliverTx returns immediately. This can happen even with the first transaction in a block, as BeginBlock itself can consume gas. If not, the transaction is processed normally. At the end of DeliverTx, the gas tracked by ctx.BlockGasMeter() is increased by the amount consumed to process the transaction:
ctx.BlockGasMeter().ConsumeGas(
	ctx.GasMeter().GasConsumedToLimit(),
	"block gas meter",
)

AnteHandler

The AnteHandler is run for every transaction during CheckTx and DeliverTx, before a Protobuf Msg service method for each sdk.Msg in the transaction. The anteHandler is not implemented in the core Cosmos SDK but in a module. That said, most applications today use the default implementation defined in the auth module. Here is what the anteHandler is intended to do in a normal Cosmos SDK application:
  • Verify that the transactions are of the correct type. Transaction types are defined in the module that implements the anteHandler, and they follow the transaction interface:
package types

import (

	"encoding/json"
	fmt "fmt"
    "github.com/cosmos/gogoproto/proto"
    "github.com/cosmos/cosmos-sdk/codec"
	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)

type (
	/ Msg defines the interface a transaction message must fulfill.
	Msg interface {
    proto.Message

		/ ValidateBasic does a simple validation check that
		/ doesn't require access to any other information.
		ValidateBasic()

error

		/ GetSigners returns the addrs of signers that must sign.
		/ CONTRACT: All signatures must be present to be valid.
		/ CONTRACT: Returns addrs in some deterministic order.
		GetSigners() []AccAddress
}

	/ Fee defines an interface for an application application-defined concrete
	/ transaction type to be able to set and return the transaction fee.
	Fee interface {
    GetGas()

uint64
		GetAmount()

Coins
}

	/ Signature defines an interface for an application application-defined
	/ concrete transaction type to be able to set and return transaction signatures.
	Signature interface {
    GetPubKey()

cryptotypes.PubKey
		GetSignature() []byte
}

	/ Tx defines the interface a transaction must fulfill.
	Tx interface {
		/ GetMsgs gets the all the transaction's messages.
		GetMsgs() []Msg

		/ ValidateBasic does a simple and lightweight validation check that doesn't
		/ require access to any other information.
		ValidateBasic()

error
}

	/ FeeTx defines the interface to be implemented by Tx to use the FeeDecorators
	FeeTx interface {
    Tx
		GetGas()

uint64
		GetFee()

Coins
		FeePayer()

AccAddress
		FeeGranter()

AccAddress
}

	/ TxWithMemo must have GetMemo()

method to use ValidateMemoDecorator
	TxWithMemo interface {
    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)

/ MsgTypeURL returns the TypeURL of a `sdk.Msg`.
func MsgTypeURL(msg Msg)

string {
    return "/" + proto.MessageName(msg)
}

/ GetMsgFromTypeURL returns a `sdk.Msg` message type from a type URL
func GetMsgFromTypeURL(cdc codec.Codec, input string) (Msg, error) {
    var msg Msg
	bz, err := json.Marshal(struct {
    Type string `json:"@type"`
}{
    Type: input,
})
    if err != nil {
    return nil, err
}
    if err := cdc.UnmarshalInterfaceJSON(bz, &msg); err != nil {
    return nil, fmt.Errorf("failed to determine sdk.Msg for %s URL : %w", input, err)
}

return msg, nil
}
This enables developers to play with various types for the transaction of their application. In the default auth module, the default transaction type is Tx:
// Tx is the standard type used for broadcasting transactions.
message Tx {
  // body is the processable content of the transaction
  TxBody body = 1;

  // auth_info is the authorization related content of the transaction,
  // specifically signers, signer modes and fee
  AuthInfo auth_info = 2;

  // signatures is a list of signatures that matches the length and order of
  // AuthInfo's signer_infos to allow connecting signature meta information like
  // public key and signing mode by position.
  repeated bytes signatures = 3;
}
  • Verify signatures for each message contained in the transaction. Each message should be signed by one or multiple sender(s), and these signatures must be verified in the anteHandler.
  • During CheckTx, verify that the gas prices provided with the transaction is greater than the local min-gas-prices (as a reminder, gas-prices can be deducted from the following equation: fees = gas * gas-prices). min-gas-prices is a parameter local to each full-node and used during CheckTx to discard transactions that do not provide a minimum amount of fees. This ensures that the mempool cannot be spammed with garbage transactions.
  • Verify that the sender of the transaction has enough funds to cover for the fees. When the end-user generates a transaction, they must indicate 2 of the 3 following parameters (the third one being implicit): fees, gas and gas-prices. This signals how much they are willing to pay for nodes to execute their transaction. The provided gas value is stored in a parameter called GasWanted for later use.
  • Set newCtx.GasMeter to 0, with a limit of GasWanted. This step is crucial, as it not only makes sure the transaction cannot consume infinite gas, but also that ctx.GasMeter is reset in-between each DeliverTx (ctx is set to newCtx after anteHandler is run, and the anteHandler is run each time DeliverTx is called).
As explained above, the anteHandler returns a maximum limit of gas the transaction can consume during execution called GasWanted. The actual amount consumed in the end is denominated GasUsed, and we must therefore have GasUsed =< GasWanted. Both GasWanted and GasUsed are relayed to the underlying consensus engine when DeliverTx returns.