Skip to main content

Module Overview

The Experimental EVM Mempool is a unified mempool implementation that manages both EVM and Cosmos SDK transactions in a single pool. Unlike the other EVM-specific components (VM, FeeMarket, ERC20, PreciseBank), the mempool is not a Cosmos SDK module but rather a standalone component that integrates at the application level.
The mempool is configured in app.go during application initialization, not through genesis state like other modules.
Key Features:
  • Dual Transaction Support: Handles both EVM (MsgEthereumTx) and native Cosmos transactions
  • Unified Ordering: Priority-based transaction selection across both transaction types
  • Nonce Management: Automatic nonce sequencing for EVM transactions with gap handling
  • Fee-Based Prioritization: Transactions ordered by gas price/priority fee
  • Ethereum Compatibility: Compatible with standard Ethereum transaction pool semantics
Source Code:

Architecture

Transaction Flow

                    ┌─────────────────────────────────┐
                    │  Incoming Transaction (CheckTx)  │
                    └────────────────┬────────────────┘

                    ┌────────────────▼────────────────┐
                    │     ExperimentalEVMMempool      │
                    │  (mempool/mempool.go:43-66)     │
                    └────────────────┬────────────────┘

                ┌────────────────────┴────────────────────┐
                │                                         │
    ┌───────────▼──────────┐              ┌──────────────▼──────────┐
    │   EVM Transaction?   │              │   Cosmos Transaction?   │
    │  (MsgEthereumTx)     │              │   (Other msg types)     │
    └───────────┬──────────┘              └──────────────┬──────────┘
                │                                        │
    ┌───────────▼──────────┐              ┌──────────────▼──────────┐
    │   TxPool + Legacy    │              │   PriorityNonceMempool  │
    │   Pool (EVM Pool)    │              │   (Cosmos Pool)         │
    │  (txpool/txpool.go)  │              │   (SDK mempool)         │
    └───────────┬──────────┘              └──────────────┬──────────┘
                │                                        │
                │      ┌─────────────────────────┐       │
                └──────►  Combined Iterator      ◄───────┘
                       │  (iterator.go:30-52)    │
                       │  Fee-based ordering     │
                       └────────────┬────────────┘

                       ┌────────────▼────────────┐
                       │   Block Proposal        │
                       │   (PrepareProposal)     │
                       └─────────────────────────┘

Component Structure

The mempool consists of three primary components:
  1. ExperimentalEVMMempool (mempool.go:43-66)
    • Top-level coordinator routing transactions to appropriate pools
    • Provides unified iterator for transaction selection
    • Manages lifecycle and synchronization
  2. EVM Transaction Pool (txpool/legacypool/legacypool.go:207-228)
    • Handles EVM transactions with Ethereum-compatible semantics
    • Manages pending and queued transaction lists
    • Implements nonce-based ordering and replacement logic
  3. Cosmos Transaction Pool (SDK PriorityNonceMempool)
    • Standard Cosmos SDK priority-based mempool
    • Handles non-EVM transaction types
    • Integrates with existing Cosmos SDK infrastructure

Integration in app.go

The mempool is initialized during application construction in app.go:
// From evmd/app.go:722-743
// Get the block gas limit from genesis file
blockGasLimit := evmconfig.GetBlockGasLimit(appOpts, logger)
// Get MinTip from app.toml or cli flag configuration
minTip := evmconfig.GetMinTip(appOpts, logger)

mempoolConfig := &evmmempool.EVMMempoolConfig{
    AnteHandler:   app.GetAnteHandler(),
    BlockGasLimit: blockGasLimit,
    MinTip:        minTip,
}

evmMempool := evmmempool.NewExperimentalEVMMempool(
    app.CreateQueryContext,
    logger,
    app.EVMKeeper,
    app.FeeMarketKeeper,
    app.txConfig,
    app.clientCtx,
    mempoolConfig,
)
app.EVMMempool = evmMempool

app.SetMempool(evmMempool)
checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
app.SetCheckTxHandler(checkTxHandler)

abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
abciProposalHandler.SetSignerExtractionAdapter(
    evmmempool.NewEthSignerExtractionAdapter(
        sdkmempool.NewDefaultSignerExtractionAdapter(),
    ),
)
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
Integration Requirements:
Create EVMMempoolConfig with required parameters from genesis and app.toml:
type EVMMempoolConfig struct {
    LegacyPoolConfig *legacypool.Config  // Optional: EVM pool settings
    CosmosPoolConfig *sdkmempool.PriorityNonceMempoolConfig[math.Int]  // Optional
    AnteHandler      sdk.AnteHandler     // Required: Transaction validation
    BroadCastTxFn    func([]*ethtypes.Transaction) error  // Optional
    BlockGasLimit    uint64              // Required: From consensus params
    MinTip           *uint256.Int        // Optional: Minimum priority fee
}
Source: mempool/mempool.go:69-79
Create the mempool instance and set it as the application mempool:
evmMempool := evmmempool.NewExperimentalEVMMempool(
    app.CreateQueryContext,  // Context provider for historical queries
    logger,                  // Logger instance
    app.EVMKeeper,          // VM Keeper for EVM state access
    app.FeeMarketKeeper,    // Fee market for base fee queries
    app.txConfig,           // Transaction encoding/decoding
    app.clientCtx,          // Client context for broadcasting
    mempoolConfig,          // Configuration from step 1
)

app.SetMempool(evmMempool)  // Register as application mempool
Source: mempool/mempool.go:85-187
Configure the CheckTx handler to validate incoming transactions:
checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
app.SetCheckTxHandler(checkTxHandler)
The CheckTx handler:
  • Validates transaction format and signatures
  • Checks nonce sequencing for EVM transactions
  • Verifies sufficient balance for gas fees
  • Routes valid transactions to appropriate pools
Source: mempool/check_tx.go
Set up the ABCI proposal handler for block production:
abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
abciProposalHandler.SetSignerExtractionAdapter(
    evmmempool.NewEthSignerExtractionAdapter(
        sdkmempool.NewDefaultSignerExtractionAdapter(),
    ),
)
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
The proposal handler:
  • Selects transactions from mempool for inclusion in blocks
  • Orders transactions by priority (gas price)
  • Respects block gas limit and consensus constraints
  • Handles both EVM and Cosmos transaction signatures
Source: SDK baseapp/abci.go

Configuration Parameters

Block Gas Limit

consensus_params.block.max_gas
int64
default:"-1 (unlimited)"
Maximum gas allowed per block. This parameter is not in app state but in the genesis consensus parameters.Configuration Location: ~/.evmd/config/genesis.json
{
  "consensus_params": {
    "block": {
      "max_bytes": "22020096",
      "max_gas": "100000000"
    }
  }
}
Valid Values:
  • -1: Unlimited gas (not recommended for production)
  • > 0: Maximum gas units per block
  • Recommended: 50000000 to 100000000 for typical chains
What It Does:
  • Limits total gas consumption per block
  • Prevents DoS attacks via expensive operations
  • Used by mempool to estimate how many transactions fit in a block
  • Affects transaction throughput and block production time
Code Reference: config/server_app_options.go:23-66
# View current max_gas setting
jq '.consensus_params.block.max_gas' ~/.evmd/config/genesis.json

# Set max_gas before chain start
jq '.consensus_params.block.max_gas = 100000000' genesis.json > genesis_tmp.json
mv genesis_tmp.json genesis.json
The block gas limit cannot be changed after chain start without a coordinated upgrade. Plan carefully based on expected transaction volume.

Minimum Priority Fee (MinTip)

evm.min-tip
uint64
default:"0"
Minimum priority fee (tip) required for EVM transactions to enter the mempool.Configuration Location: ~/.evmd/config/app.toml
[evm]
# Minimum priority fee for mempool acceptance (in wei)
min-tip = 1000000000  # 1 gwei
Valid Values:
  • 0: No minimum tip (accept any priority fee)
  • > 0: Minimum tip in wei (1 gwei = 1000000000 wei)
What It Does:
  • Filters out low-priority transactions
  • Protects against mempool spam
  • Does not affect base fee (controlled by FeeMarket module)
  • Only applies to EVM transactions (MsgEthereumTx)
Code Reference:
[evm]
# Minimum priority fee (in wei)
# 0 = no minimum (default)
# 1000000000 = 1 gwei
# 1000000000000 = 1000 gwei
min-tip = 1000000000
Difference between MinTip and BaseFee:
  • BaseFee: Dynamic fee set by FeeMarket module, adjusts per block (EIP-1559)
  • MinTip: Static minimum priority fee set in app.toml, filters mempool entries
  • Effective Fee: max(base_fee, min_tip) + priority_fee

Legacy Pool Configuration

The EVM transaction pool uses Ethereum’s legacy pool implementation with the following parameters:
Journal: File path for persisting local transactions across node restartsRejournal: Time interval to regenerate the journal file
// Default from legacypool.go:158-159
Journal:   "transactions.rlp"
Rejournal: time.Hour
What It Does:
  • Persists pending transactions to disk
  • Allows recovery after unexpected node restarts
  • Only applies to “local” transactions (submitted directly to this node)
  • Remote transactions are not journaled
Configuration (requires code modification in app.go):
legacyConfig := legacypool.DefaultConfig
legacyConfig.Journal = filepath.Join(homePath, "data", "evm_transactions.rlp")
legacyConfig.Rejournal = 30 * time.Minute

mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    // ... other fields
}
Source: mempool/txpool/legacypool/legacypool.go:142-143
PriceLimit: Minimum gas price (in wei) for transaction acceptancePriceBump: Minimum percentage increase required to replace an existing transaction with the same nonce
// Default from legacypool.go:161-162
PriceLimit: 1      // 1 wei minimum
PriceBump:  10     // 10% increase required
What PriceLimit Does:
  • Filters out transactions with gas price below threshold
  • Prevents mempool spam with zero-fee transactions
  • Separate from MinTip (which applies to priority fee only)
What PriceBump Does:
  • Enables transaction replacement (Replace-By-Fee)
  • If account sends two transactions with same nonce, new one must pay ≥ 10% more
  • Prevents trivial transaction churn
Example: If existing transaction has gas price of 10 gwei, replacement must have ≥ 11 gwei.Configuration:
legacyConfig := legacypool.DefaultConfig
legacyConfig.PriceLimit = 1000000000  // 1 gwei minimum
legacyConfig.PriceBump = 20           // 20% increase for replacement

mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    // ... other fields
}
Source:
AccountSlots: Maximum executable transactions per accountGlobalSlots: Maximum total executable transactions across all accounts
// Default from legacypool.go:164-165
AccountSlots: 16              // Per-account limit
GlobalSlots:  4096 + 1024     // 5120 total (urgent + floating)
What It Does:
  • Executable transactions = transactions with correct nonce that can be included in next block
  • AccountSlots prevents single account from dominating mempool
  • GlobalSlots limits total memory usage
  • When limits exceeded, lowest-priced transactions are evicted
Relationship:
  • GlobalSlots ≥ AccountSlots (must allow at least one full account)
  • Typical ratio: GlobalSlots = 256 to 512 × AccountSlots
Configuration:
legacyConfig := legacypool.DefaultConfig
legacyConfig.AccountSlots = 32    // Allow 32 pending tx per account
legacyConfig.GlobalSlots = 8192   // 8k total pending transactions

mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    // ... other fields
}
Source:
AccountQueue: Maximum queued (future) transactions per accountGlobalQueue: Maximum total queued transactions across all accounts
// Default from legacypool.go:166-167
AccountQueue: 64    // Per-account queued limit
GlobalQueue:  1024  // Total queued limit
What It Does:
  • Queued transactions = transactions with nonce gaps (not immediately executable)
  • Held until gap-filling transactions arrive
  • AccountQueue limits queued transactions per account
  • GlobalQueue caps total memory for future transactions
Example: If account nonce is 5, transactions with nonce 6,7,8 are pending, but transaction with nonce 10 is queued (gap at nonce 9).Configuration:
legacyConfig := legacypool.DefaultConfig
legacyConfig.AccountQueue = 128   // Allow 128 queued tx per account
legacyConfig.GlobalQueue = 2048   // 2k total queued transactions

mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    // ... other fields
}
Source:
Lifetime: Maximum time non-executable (queued) transactions remain in mempool
// Default from legacypool.go:169
Lifetime: 3 * time.Hour
What It Does:
  • Queued transactions older than lifetime are evicted
  • Prevents mempool from filling with stale future transactions
  • Only applies to queued transactions (nonce gaps), not pending
  • Eviction checked periodically (every 1 minute by default)
Typical Values:
  • Short-lived: 1-3 hours (good for high-activity chains)
  • Long-lived: 12-24 hours (good for low-activity chains)
Configuration:
legacyConfig := legacypool.DefaultConfig
legacyConfig.Lifetime = 6 * time.Hour  // 6 hours before eviction

mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    // ... other fields
}
Source:
NoLocals: Disable special handling for local transactions
// Default from legacypool.go:141
NoLocals: false  // Local transaction tracking enabled
What It Does:
  • Local transactions = submitted directly to this node via RPC
  • Remote transactions = received from other nodes via gossip
  • When NoLocals = false:
    • Local transactions are journaled to disk
    • Local transactions have slightly higher priority
    • Local transactions bypass some price checks
  • When NoLocals = true:
    • All transactions treated equally
    • No journaling (faster, less disk I/O)
Configuration:
legacyConfig := legacypool.DefaultConfig
legacyConfig.NoLocals = true  // Disable local transaction priority

mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    // ... other fields
}
Source: mempool/txpool/legacypool/legacypool.go:141

Complete Configuration Example

Here’s a complete example showing how to configure the mempool with custom settings in app.go:
// In your NewApp function in app.go

// 1. Get configuration from genesis and app.toml
blockGasLimit := evmconfig.GetBlockGasLimit(appOpts, logger)
minTip := evmconfig.GetMinTip(appOpts, logger)

// 2. Configure Legacy Pool (EVM transactions)
legacyConfig := legacypool.DefaultConfig
legacyConfig.Journal = filepath.Join(cast.ToString(appOpts.Get(flags.FlagHome)), "data", "evm_txpool.rlp")
legacyConfig.Rejournal = 30 * time.Minute
legacyConfig.PriceLimit = 1000000000  // 1 gwei minimum
legacyConfig.PriceBump = 15           // 15% replacement bump
legacyConfig.AccountSlots = 32        // 32 pending per account
legacyConfig.GlobalSlots = 8192       // 8k total pending
legacyConfig.AccountQueue = 128       // 128 queued per account
legacyConfig.GlobalQueue = 2048       // 2k total queued
legacyConfig.Lifetime = 6 * time.Hour // 6 hour lifetime
legacyConfig.NoLocals = false         // Enable local tx tracking

// 3. Configure Cosmos Pool (optional, uses defaults if nil)
cosmosConfig := sdkmempool.PriorityNonceMempoolConfig[math.Int]{}
cosmosConfig.TxPriority = sdkmempool.TxPriority[math.Int]{
    GetTxPriority: func(goCtx context.Context, tx sdk.Tx) math.Int {
        // Custom priority function for Cosmos transactions
        ctx := sdk.UnwrapSDKContext(goCtx)
        cosmosTxFee, ok := tx.(sdk.FeeTx)
        if !ok {
            return math.ZeroInt()
        }
        found, coin := cosmosTxFee.GetFee().Find(app.EVMKeeper.GetEvmCoinInfo(ctx).Denom)
        if !found {
            return math.ZeroInt()
        }
        gasPrice := coin.Amount.Quo(math.NewIntFromUint64(cosmosTxFee.GetGas()))
        return gasPrice
    },
    Compare: func(a, b math.Int) int {
        return a.BigInt().Cmp(b.BigInt())
    },
    MinValue: math.ZeroInt(),
}

// 4. Create mempool configuration
mempoolConfig := &evmmempool.EVMMempoolConfig{
    LegacyPoolConfig: &legacyConfig,
    CosmosPoolConfig: &cosmosConfig,
    AnteHandler:      app.GetAnteHandler(),
    BlockGasLimit:    blockGasLimit,
    MinTip:           minTip,
    // BroadCastTxFn: nil,  // Uses default broadcast function
}

// 5. Initialize mempool
evmMempool := evmmempool.NewExperimentalEVMMempool(
    app.CreateQueryContext,
    logger,
    app.EVMKeeper,
    app.FeeMarketKeeper,
    app.txConfig,
    app.clientCtx,
    mempoolConfig,
)
app.EVMMempool = evmMempool

// 6. Register with application
app.SetMempool(evmMempool)
checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
app.SetCheckTxHandler(checkTxHandler)

// 7. Configure proposal handler
abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
abciProposalHandler.SetSignerExtractionAdapter(
    evmmempool.NewEthSignerExtractionAdapter(
        sdkmempool.NewDefaultSignerExtractionAdapter(),
    ),
)
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())

Transaction Lifecycle

EVM Transaction Flow

┌─────────────────────────────────────────────────────────────┐
│ 1. Transaction Submission (eth_sendRawTransaction)          │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ 2. CheckTx (mempool/check_tx.go)                            │
│    - Decode transaction                                     │
│    - Verify signature                                       │
│    - Check nonce (account nonce ≤ tx nonce)                │
│    - Verify sufficient balance                             │
│    - Run AnteHandler validation                            │
└──────────────────────────┬──────────────────────────────────┘

                    ┌──────┴──────┐
                    │             │
        ┌───────────▼───┐    ┌────▼───────────┐
        │  Valid Nonce  │    │  Nonce Gap     │
        │  (Sequential) │    │  (Future)      │
        └───────┬───────┘    └────┬───────────┘
                │                 │
┌───────────────▼──────────┐  ┌──▼────────────────────────────┐
│ 3a. Insert Pending       │  │ 3b. Insert Queued             │
│     (mempool.go:214-226) │  │     (mempool.go:243-268)      │
│     - Immediately ready  │  │     - Waiting for earlier tx  │
│     - Can be included    │  │     - Held until gap filled   │
└───────────────┬──────────┘  └──┬────────────────────────────┘
                │                │
                │  ┌─────────────▼──────────────────────────┐
                │  │ Nonce gap filled by subsequent tx?     │
                │  │ (Yes: promote to pending)              │
                │  └─────────────┬──────────────────────────┘
                │                │
                └────────────────┘

┌────────────────────────▼─────────────────────────────────────┐
│ 4. Transaction Selection (iterator.go)                       │
│    - Order by gas price (highest first)                     │
│    - Group by account, maintain nonce order                 │
│    - Respect block gas limit                                │
│    - Filter by MinTip and BaseFee                           │
└────────────────────────┬─────────────────────────────────────┘

┌────────────────────────▼─────────────────────────────────────┐
│ 5. Block Inclusion (PrepareProposal)                         │
│    - Add to block until gas limit reached                   │
│    - Execute transactions                                   │
└────────────────────────┬─────────────────────────────────────┘

┌────────────────────────▼─────────────────────────────────────┐
│ 6. Removal (mempool.go:296-334)                             │
│    - Successful: automatic removal by nonce advancement     │
│    - Failed: manual removal if non-recoverable error        │
│    - Nonce gap errors: kept in mempool                      │
└─────────────────────────────────────────────────────────────┘

Nonce Gap Handling

The mempool implements sophisticated nonce gap handling to support Ethereum’s sequential nonce requirement:
1

Transaction Arrives

Account has nonce 5 on-chain. Transactions arrive:
  • Nonce 6: Pending (can execute immediately)
  • Nonce 7: Queued (waiting for nonce 6)
  • Nonce 10: Queued (waiting for nonces 7, 8, 9)
2

Gap Detected

Transaction with nonce 10 cannot execute until nonces 6-9 are processed. It enters the queued pool.Code: mempool/mempool.go:243-268
3

Gap Filled

When transactions with nonces 7, 8, 9 arrive and are validated:
  • Nonce 7 promotes to pending (after 6 executes)
  • Nonce 8 promotes to pending (after 7 executes)
  • Nonce 9 promotes to pending (after 8 executes)
  • Nonce 10 promotes to pending (after 9 executes)
This happens automatically via the broadcast function.Code: mempool/mempool.go:116-126
4

Eviction

If queued transaction sits too long (beyond Lifetime parameter), it’s evicted from mempool.Default Lifetime: 3 hours (legacypool.go:169)

Cosmos Transaction Flow

Cosmos transactions follow a simpler priority-based flow:
┌─────────────────────────────────────────────────────────────┐
│ 1. Transaction Submission (/cosmos/tx/v1beta1/txs)          │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ 2. CheckTx (mempool/check_tx.go)                            │
│    - Decode transaction                                     │
│    - Verify signatures                                      │
│    - Check account sequence                                │
│    - Verify sufficient balance                             │
│    - Run AnteHandler validation                            │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ 3. Insert into Cosmos Pool (mempool.go:228-236)            │
│    - Calculate priority (gas price)                         │
│    - Insert into priority queue                             │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ 4. Transaction Selection (iterator.go)                      │
│    - Order by priority (highest first)                     │
│    - Combine with EVM transactions                          │
│    - Respect block gas limit                                │
└──────────────────────────┬──────────────────────────────────┘

┌──────────────────────────▼──────────────────────────────────┐
│ 5. Block Inclusion & Removal                                │
│    - Execute in block                                       │
│    - Remove from mempool after commit                       │
└─────────────────────────────────────────────────────────────┘

Monitoring and Metrics

Query Mempool State

# View pending EVM transactions
evmd query txpool content

# Count transactions in mempool
evmd query txpool status

# Inspect specific transaction
evmd query txpool inspect [tx-hash]

Geth Metrics

The mempool exposes Ethereum-compatible metrics at the configured metrics address:
# In app.toml
[evm]
geth-metrics-address = "127.0.0.1:8100"
Available Metrics:
  • txpool/pending: Number of pending transactions
  • txpool/queued: Number of queued transactions
  • txpool/slots: Total slots used
  • txpool/discard: Transactions discarded
  • txpool/replace: Transactions replaced
  • txpool/underpriced: Transactions rejected for low price
  • txpool/throttle: Transactions throttled
  • txpool/reorgtime: Time spent reorganizing mempool
Access Metrics:
curl http://127.0.0.1:8100/debug/metrics
Source: mempool/txpool/legacypool/legacypool.go:86-119

Log Levels

Enable debug logging for detailed mempool activity:
evmd start --log_level debug
Relevant Log Lines:
  • "inserting transaction into mempool": Transaction entering mempool
  • "inserting EVM transaction": EVM tx routed to EVM pool
  • "inserting Cosmos transaction": Cosmos tx routed to Cosmos pool
  • "broadcasting EVM transactions": Queued tx promoted to pending
  • "removing transaction from mempool": Transaction removal
Source: mempool/mempool.go:212-234

Troubleshooting

Symptoms: Transactions remain in mempool indefinitely without inclusion in blocks.Possible Causes:
  1. Nonce Gap
    • Check if transaction has future nonce
    • Verify previous nonce transactions exist
    • Query: evmd query txpool content and check “queued” section
  2. Insufficient Priority Fee
    • Transaction may be below MinTip threshold
    • Increase gas price or priority fee
    • Check current MinTip: grep "min-tip" ~/.evmd/config/app.toml
  3. Mempool Full
    • GlobalSlots or GlobalQueue limits reached
    • Lower-priced transactions are evicted
    • Increase gas price for priority
  4. Block Gas Limit
    • Transaction gas exceeds block gas limit
    • Check: evmd query consensus params
    • Must deploy contract in multiple transactions or increase block limit
Solutions:
# Check mempool status
evmd query txpool status

# View stuck transactions
evmd query txpool content | jq '.queued'

# Check transaction by hash
evmd query txpool inspect [tx-hash]

# Resubmit with higher gas price (via wallet)
Symptoms: Transaction rejected with “nonce too low” or similar error.Cause: Account nonce has advanced beyond submitted transaction nonce.Explanation:
  • On-chain nonce: Last executed transaction nonce + 1
  • Transaction nonce must equal current on-chain nonce
  • If transaction nonce < on-chain nonce: already executed or replaced
Solutions:
# 1. Query current account nonce
evmd query auth account [address] | jq '.account.sequence'

# 2. Query account via EVM (returns next expected nonce)
curl -X POST http://localhost:8545 -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["[address]","latest"],"id":1}'

# 3. Resubmit transaction with correct nonce
# (use wallet/client to generate new transaction)
Never manually set nonces unless you understand Ethereum nonce mechanics. Use wallet automatic nonce management.
Symptoms: Attempting to replace pending transaction but getting “replacement transaction underpriced” error.Cause: New transaction doesn’t meet PriceBump threshold (default 10%).Explanation:
  • To replace transaction with same nonce, new gas price must be ≥ (old gas price × 1.10)
  • This prevents trivial transaction churn
  • PriceBump is configurable in LegacyPoolConfig
Solutions:
# Check existing transaction price
evmd query txpool content | jq '.pending."[address]"'

# Calculate required price (example: old price 10 gwei, need 11+ gwei)
# Resubmit with 15-20% higher gas price to ensure acceptance
Code Reference: mempool/txpool/legacypool/legacypool.go:162
Symptoms: Transactions remain in mempool after successful block inclusion.Cause: Likely a bug in removal logic or nonce tracking.Debug Steps:
# 1. Enable debug logging
evmd start --log_level debug

# 2. Check for removal logs
# Look for: "removing transaction from mempool"
# And: "manually removing EVM transaction"

# 3. Query mempool state
evmd query txpool status

# 4. Check if transactions were actually executed
evmd query tx [tx-hash]

# 5. Restart node (mempool rebuilds from current state)
Workaround: Restart the node - mempool will repopulate with only valid pending transactions.Source: mempool/mempool.go:296-334
Symptoms: Node memory usage grows unbounded, potential OOM kills.Cause: Mempool configuration allows too many transactions.Solutions:
  1. Reduce Global Slot/Queue Limits
legacyConfig := legacypool.DefaultConfig
legacyConfig.GlobalSlots = 2048  // Reduce from 5120
legacyConfig.GlobalQueue = 512   // Reduce from 1024
  1. Reduce Per-Account Limits
legacyConfig.AccountSlots = 8   // Reduce from 16
legacyConfig.AccountQueue = 32  // Reduce from 64
  1. Reduce Lifetime
legacyConfig.Lifetime = 1 * time.Hour  // Reduce from 3 hours
  1. Increase MinTip
# In app.toml
[evm]
min-tip = 1000000000  # 1 gwei - filters low-priority spam
Memory Estimation:
  • Each transaction: ~1-4 KB
  • 5120 pending + 1024 queued ≈ 6-24 MB baseline
  • Adjust based on available memory and expected load
Monitoring:
# Check mempool size
evmd query txpool status

# View slot usage
curl http://127.0.0.1:8100/debug/metrics | grep txpool/slots

Source Code References

ComponentFileLinesDescription
ExperimentalEVMMempoolmempool/mempool.go43-66Main mempool structure
EVMMempoolConfigmempool/mempool.go69-79Configuration struct
NewExperimentalEVMMempoolmempool/mempool.go85-187Constructor function
Insertmempool/mempool.go205-237Transaction insertion
Selectmempool/mempool.go274-284Transaction selection
Removemempool/mempool.go296-334Transaction removal
LegacyPool Configlegacypool/legacypool.go138-154EVM pool configuration
DefaultConfiglegacypool/legacypool.go157-170Default EVM pool settings
GetBlockGasLimitconfig/server_app_options.go23-66Extract block gas limit from genesis
GetMinTipconfig/server_app_options.go82-94Extract MinTip from app.toml
App Integrationevmd/app.go722-743Mempool initialization in app
CheckTx Handlermempool/check_tx.go-Transaction validation
Iteratormempool/iterator.go-Unified transaction iterator
EVM Config Defaultsserver/config/config.go67-68MinTip default values

Additional Resources


Next Steps: After configuring the mempool, proceed to update your main chain building guide and customization checklist to reference this documentation.