在待处理状态期间,交易发起者可以随时更改交易字段。他们可以通过发送另一笔具有相同 nonce 的交易来实现。

前置阅读​

Cosmos EVM 与 Ethereum​

在 Ethereum 中,待处理区块会在矿工将其排入出块队列时生成。这些待处理区块包含矿工根据 gas 奖励高低挑选出的待处理交易。之所以存在这种机制,是因为 Ethereum 网络无法提供即时的区块终局性。区块以概率终局性的方式提交,这意味着随着时间推移以及后续区块增多,交易和区块被回滚的可能性会逐渐降低。 Cosmos EVM 的设计方式不同,因为它使用 CometBFT 共识,为交易提供即时终局性。虽然这里不存在可被重组的“待处理区块”概念,但 Cosmos EVM 实现了一个实验性的 EVM mempool,以提供兼容 Ethereum 的待处理状态功能。

兼容 EVM 的 Mempool

实验性的 EVM mempool 引入了一个两层系统,为 Cosmos EVM 带来类似 Ethereum 的待处理状态行为:

交易状态

待处理交易

nonce 正确且可立即执行的交易。它们位于公共 mempool 中,会广播给对等节点,并已准备好被纳入区块。

排队交易

具有未来 nonce(nonce 缺口)的交易,会存储在本地。在被提升为待处理状态之前,它们会等待更早的交易先执行。

与传统 Cosmos 的关键差异

  1. Nonce 缺口处理:不同于会拒绝乱序交易的传统 Cosmos 链,EVM mempool 会先在本地将这些交易排队,直到缺口被填补。
  2. 基于费用的优先级:EVM 和 Cosmos 交易都会基于其有效小费而不是 FIFO 顺序进行公平竞争:
    • EVM 交易:优先级 = gas_tip_cap 或 min(gas_tip_cap, gas_fee_cap - base_fee)
    • Cosmos 交易:优先级 = (fee_amount / gas_limit) - base_fee
  3. 交易替换:支持使用相同 nonce 的更高费用版本替换待处理交易,从而实现 Ethereum 钱包常见的“加速”功能。

待处理状态查询​

有了实验性的 EVM mempool,待处理状态查询现在可以体现出更兼容 Ethereum 的视图:

交易池检查

该 mempool 提供了专用 RPC 方法,用于检查待处理和排队交易:
返回待处理和排队交易的数量:
{
  "pending": "0x10",  // 16 pending transactions
  "queued": "0x5"     // 5 queued transactions
}
返回池中所有交易的详细信息,并按账户和 nonce 组织。
提供便于人工阅读的交易摘要,用于调试。
完整细节请参阅 JSON-RPC Methods 文档。

待处理状态行为

当查询使用 "pending" 作为区块参数时:
  1. 余额查询:反映在应用该账户的所有待处理交易后的账户余额
  2. Nonce 查询:在考虑所有待处理交易的情况下,返回下一个可用 nonce
  3. Gas 估算:将可能影响 gas 成本的待处理交易考虑在内
待处理状态查询取决于各节点本地 mempool 的视图。不同节点可能会根据各自交易池中的内容返回不同结果。

支持待处理状态的 JSON-RPC 调用​

以下 RPC 方法支持 "pending" 区块参数: 此外,txpool_* 命名空间还提供了用于检查 mempool 的专用方法:

实用示例

监控交易状态

// Check if transaction is pending
const tx = await provider.getTransaction(txHash);
if (tx && !tx.blockNumber) {
  console.log("Transaction is pending");

  // Check pool status
  const poolStatus = await provider.send("txpool_status", []);
  console.log(`Pool has ${poolStatus.pending} pending, ${poolStatus.queued} queued`);
}

处理 Nonce 缺口

// Send transactions with nonce gaps (they'll be queued)
await wallet.sendTransaction({nonce: 100, ...});  // Executes immediately
await wallet.sendTransaction({nonce: 102, ...});  // Queued (gap at 101)
await wallet.sendTransaction({nonce: 101, ...});  // Fills gap, both execute

交易替换

// Speed up a pending transaction
const originalTx = await wallet.sendTransaction({
  nonce: 100,
  gasPrice: parseUnits("20", "gwei")
});

// Replace with higher fee
const fasterTx = await wallet.sendTransaction({
  nonce: 100,  // Same nonce
  gasPrice: parseUnits("30", "gwei")  // Higher fee
});

架构细节

若要详细了解待处理状态的管理方式:
During the pending state, the transaction initiator is allowed to change the transaction fields at any time. They can do so by sending another transaction with the same nonce.

Prerequisite Readings​

Cosmos EVM vs Ethereum​

In Ethereum, pending blocks are generated as they are queued for production by miners. These pending blocks include pending transactions that are picked out by miners, based on the highest reward paid in gas. This mechanism exists as block finality is not possible on the Ethereum network. Blocks are committed with probabilistic finality, which means that transactions and blocks become less likely to become reverted as more time (and blocks) passes. Cosmos EVM is designed differently as it uses CometBFT consensus which provides instant finality for transactions. While there is no concept of “pending blocks” that can be reorganized, Cosmos EVM implements an experimental EVM mempool that provides Ethereum-compatible pending state functionality.

EVM-Compliant Mempool

The experimental EVM mempool introduces a two-tiered system that brings Ethereum-like pending state behavior to Cosmos EVM:

Transaction States

Pending Transactions

Transactions with correct nonces that are immediately executable. These are in the public mempool, broadcast to peers, and ready for block inclusion.

Queued Transactions

Transactions with future nonces (nonce gaps) that are stored locally. These wait for earlier transactions to execute before being promoted to pending.

Key Differences from Traditional Cosmos

  1. Nonce Gap Handling: Unlike traditional Cosmos chains that reject out-of-order transactions, the EVM mempool queues them locally until gaps are filled.
  2. Fee-Based Priority: Both EVM and Cosmos transactions compete fairly based on their effective tips rather than FIFO ordering:
    • EVM transactions: Priority = gas_tip_cap or min(gas_tip_cap, gas_fee_cap - base_fee)
    • Cosmos transactions: Priority = (fee_amount / gas_limit) - base_fee
  3. Transaction Replacement: Supports replacing pending transactions with higher fee versions using the same nonce, enabling “speed up” functionality common in Ethereum wallets.

Pending State Queries​

With the experimental EVM mempool, pending state queries now reflect a more Ethereum-compatible view:

Transaction Pool Inspection

The mempool provides dedicated RPC methods to inspect pending and queued transactions:
Returns counts of pending and queued transactions:
{
  "pending": "0x10",  // 16 pending transactions
  "queued": "0x5"     // 5 queued transactions
}
Returns detailed information about all transactions in the pool, organized by account and nonce.
Provides human-readable summaries of transactions for debugging.
See the JSON-RPC Methods documentation for complete details.

Pending State Behavior

When making queries with “pending” as the block parameter:
  1. Balance Queries: Reflect the account balance after all pending transactions from that account are applied
  2. Nonce Queries: Return the next available nonce considering all pending transactions
  3. Gas Estimates: Account for pending transactions that may affect gas costs
Pending state queries are subjective to each node’s local mempool view. Different nodes may return different results based on their transaction pool contents.

JSON-RPC Calls Supporting Pending State​

The following RPC methods support the "pending" block parameter: Additionally, the txpool_* namespace provides specialized methods for mempool inspection:

Practical Examples

Monitoring Transaction Status

// Check if transaction is pending
const tx = await provider.getTransaction(txHash);
if (tx && !tx.blockNumber) {
  console.log("Transaction is pending");

  // Check pool status
  const poolStatus = await provider.send("txpool_status", []);
  console.log(`Pool has ${poolStatus.pending} pending, ${poolStatus.queued} queued`);
}

Handling Nonce Gaps

// Send transactions with nonce gaps (they'll be queued)
await wallet.sendTransaction({nonce: 100, ...});  // Executes immediately
await wallet.sendTransaction({nonce: 102, ...});  // Queued (gap at 101)
await wallet.sendTransaction({nonce: 101, ...});  // Fills gap, both execute

Transaction Replacement

// Speed up a pending transaction
const originalTx = await wallet.sendTransaction({
  nonce: 100,
  gasPrice: parseUnits("20", "gwei")
});

// Replace with higher fee
const fasterTx = await wallet.sendTransaction({
  nonce: 100,  // Same nonce
  gasPrice: parseUnits("30", "gwei")  // Higher fee
});

Architecture Details

For a detailed understanding of how the pending state is managed: