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.- 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
- Implementation:
mempool/mempool.go - Interfaces:
mempool/interface.go - Legacy Pool:
mempool/txpool/legacypool/legacypool.go - Configuration Helpers:
config/server_app_options.go - Integration:
evmd/app.go#L727-L743
Architecture
Transaction Flow
Component Structure
The mempool consists of three primary components:-
ExperimentalEVMMempool (
mempool.go:43-66)- Top-level coordinator routing transactions to appropriate pools
- Provides unified iterator for transaction selection
- Manages lifecycle and synchronization
-
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
-
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 inapp.go:
1. Mempool Configuration
1. Mempool Configuration
Create Source:
EVMMempoolConfig with required parameters from genesis and app.toml:mempool/mempool.go:69-792. Initialize Mempool
2. Initialize Mempool
3. Set CheckTx Handler
3. Set CheckTx Handler
Configure the CheckTx handler to validate incoming transactions: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
mempool/check_tx.go4. Configure Proposal Handler
4. Configure Proposal Handler
Set up the ABCI proposal handler for block production: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
baseapp/abci.goConfiguration Parameters
Block Gas Limit
Maximum gas allowed per block. This parameter is not in app state but in the genesis consensus parameters.Configuration Location: Valid Values:
~/.evmd/config/genesis.json-1: Unlimited gas (not recommended for production)> 0: Maximum gas units per block- Recommended:
50000000to100000000for typical chains
- 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
config/server_app_options.go:23-66Minimum Priority Fee (MinTip)
Minimum priority fee (tip) required for EVM transactions to enter the mempool.Configuration Location: Valid Values:
~/.evmd/config/app.toml0: No minimum tip (accept any priority fee)> 0: Minimum tip in wei (1 gwei = 1000000000 wei)
- Filters out low-priority transactions
- Protects against mempool spam
- Does not affect base fee (controlled by FeeMarket module)
- Only applies to EVM transactions (MsgEthereumTx)
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 & Rejournal
Journal & Rejournal
Journal: File path for persisting local transactions across node restartsRejournal: Time interval to regenerate the journal fileWhat It Does:Source:
- 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
app.go):mempool/txpool/legacypool/legacypool.go:142-143PriceLimit & PriceBump
PriceLimit & PriceBump
PriceLimit: Minimum gas price (in wei) for transaction acceptancePriceBump: Minimum percentage increase required to replace an existing transaction with the same nonceWhat PriceLimit Does:Source:
- Filters out transactions with gas price below threshold
- Prevents mempool spam with zero-fee transactions
- Separate from MinTip (which applies to priority fee only)
- Enables transaction replacement (Replace-By-Fee)
- If account sends two transactions with same nonce, new one must pay ≥ 10% more
- Prevents trivial transaction churn
AccountSlots & GlobalSlots
AccountSlots & GlobalSlots
AccountSlots: Maximum executable transactions per accountGlobalSlots: Maximum total executable transactions across all accountsWhat It Does:Source:
- 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
- GlobalSlots ≥ AccountSlots (must allow at least one full account)
- Typical ratio: GlobalSlots = 256 to 512 × AccountSlots
AccountQueue & GlobalQueue
AccountQueue & GlobalQueue
AccountQueue: Maximum queued (future) transactions per accountGlobalQueue: Maximum total queued transactions across all accountsWhat It Does:Source:
- 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
Lifetime
Lifetime
Lifetime: Maximum time non-executable (queued) transactions remain in mempoolWhat It Does:Source:
- 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)
- Short-lived: 1-3 hours (good for high-activity chains)
- Long-lived: 12-24 hours (good for low-activity chains)
mempool/txpool/legacypool/legacypool.go:153- Validation:
mempool/txpool/legacypool/legacypool.go:200-203 - Eviction interval:
mempool/txpool/legacypool/legacypool.go:81
NoLocals Flag
NoLocals Flag
NoLocals: Disable special handling for local transactionsWhat It Does:Source:
- 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)
mempool/txpool/legacypool/legacypool.go:141Complete Configuration Example
Here’s a complete example showing how to configure the mempool with custom settings inapp.go:
Transaction Lifecycle
EVM Transaction Flow
Nonce Gap Handling
The mempool implements sophisticated nonce gap handling to support Ethereum’s sequential nonce requirement: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)
Gap Detected
Transaction with nonce 10 cannot execute until nonces 6-9 are processed. It enters the queued pool.Code:
mempool/mempool.go:243-268Gap 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)
mempool/mempool.go:116-126Eviction
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:Monitoring and Metrics
Query Mempool State
Geth Metrics
The mempool exposes Ethereum-compatible metrics at the configured metrics address:txpool/pending: Number of pending transactionstxpool/queued: Number of queued transactionstxpool/slots: Total slots usedtxpool/discard: Transactions discardedtxpool/replace: Transactions replacedtxpool/underpriced: Transactions rejected for low pricetxpool/throttle: Transactions throttledtxpool/reorgtime: Time spent reorganizing mempool
mempool/txpool/legacypool/legacypool.go:86-119
Log Levels
Enable debug logging for detailed mempool activity:"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
mempool/mempool.go:212-234
Troubleshooting
Transactions Stuck in Mempool
Transactions Stuck in Mempool
Symptoms: Transactions remain in mempool indefinitely without inclusion in blocks.Possible Causes:
-
Nonce Gap
- Check if transaction has future nonce
- Verify previous nonce transactions exist
- Query:
evmd query txpool contentand check “queued” section
-
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
-
Mempool Full
- GlobalSlots or GlobalQueue limits reached
- Lower-priced transactions are evicted
- Increase gas price for priority
-
Block Gas Limit
- Transaction gas exceeds block gas limit
- Check:
evmd query consensus params - Must deploy contract in multiple transactions or increase block limit
'Nonce Too Low' Errors
'Nonce Too Low' Errors
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
'Replacement Transaction Underpriced'
'Replacement Transaction Underpriced'
Symptoms: Attempting to replace pending transaction but getting “replacement transaction underpriced” error.Cause: New transaction doesn’t meet PriceBump threshold (default 10%).Explanation:Code Reference:
- 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
mempool/txpool/legacypool/legacypool.go:162Mempool Not Removing Executed Transactions
Mempool Not Removing Executed Transactions
Symptoms: Transactions remain in mempool after successful block inclusion.Cause: Likely a bug in removal logic or nonce tracking.Debug Steps:Workaround: Restart the node - mempool will repopulate with only valid pending transactions.Source:
mempool/mempool.go:296-334High Memory Usage
High Memory Usage
Symptoms: Node memory usage grows unbounded, potential OOM kills.Cause: Mempool configuration allows too many transactions.Solutions:Memory Estimation:
- Reduce Global Slot/Queue Limits
- Reduce Per-Account Limits
- Reduce Lifetime
- Increase MinTip
- Each transaction: ~1-4 KB
- 5120 pending + 1024 queued ≈ 6-24 MB baseline
- Adjust based on available memory and expected load
Source Code References
| Component | File | Lines | Description |
|---|---|---|---|
| ExperimentalEVMMempool | mempool/mempool.go | 43-66 | Main mempool structure |
| EVMMempoolConfig | mempool/mempool.go | 69-79 | Configuration struct |
| NewExperimentalEVMMempool | mempool/mempool.go | 85-187 | Constructor function |
| Insert | mempool/mempool.go | 205-237 | Transaction insertion |
| Select | mempool/mempool.go | 274-284 | Transaction selection |
| Remove | mempool/mempool.go | 296-334 | Transaction removal |
| LegacyPool Config | legacypool/legacypool.go | 138-154 | EVM pool configuration |
| DefaultConfig | legacypool/legacypool.go | 157-170 | Default EVM pool settings |
| GetBlockGasLimit | config/server_app_options.go | 23-66 | Extract block gas limit from genesis |
| GetMinTip | config/server_app_options.go | 82-94 | Extract MinTip from app.toml |
| App Integration | evmd/app.go | 722-743 | Mempool initialization in app |
| CheckTx Handler | mempool/check_tx.go | - | Transaction validation |
| Iterator | mempool/iterator.go | - | Unified transaction iterator |
| EVM Config Defaults | server/config/config.go | 67-68 | MinTip default values |
Additional Resources
- Ethereum Transaction Pool: Geth TxPool Documentation
- EIP-1559 Specification: EIP-1559: Fee Market
- Cosmos SDK Mempool: Application Mempool
- Integration Tests:
tests/integration/mempool/ - System Tests:
tests/systemtests/mempool/
Next Steps: After configuring the mempool, proceed to update your main chain building guide and customization checklist to reference this documentation.