一个 mempool(memory 和 pool 的合成词)是节点用于存储未提交交易信息的数据结构。它相当于一个等待区,用来暂存尚未提交的交易。 CometBFT 当前支持三种 mempool:flood、nop 和 app。

1. Flood

flood mempool 使用并发链表存储交易。当收到一笔新交易时,它会先检查是否还有空间容纳该交易(size 和 max_txs_bytes 配置项),并确认交易大小没有超限(max_tx_bytes 配置项)。然后,它会使用 LRU 缓存检查这笔交易是否已经见过(cache_size 控制缓存大小)。如果所有检查都通过,并且该交易不在缓存中(意味着它是新交易),就会调用 ABCI 的 CheckTxAsync 方法。ABCI 应用会按照自己的规则验证这笔交易。 如果 ABCI 应用认为该交易有效,它就会被加入链表。 该 mempool 的名称(flood)来自它的传播机制。当一笔新交易被加入链表后,mempool 会将其发送给所有已连接的对等节点。对等节点再将该交易继续 gossip 给它们各自的对等节点,如此反复。可以说每笔交易都会“泛洪”整个网络,因此得名 flood。 请注意,有两个实验性配置项 experimental_max_gossip_connections_to_persistent_peers 和 experimental_max_gossip_connections_to_non_persistent_peers 可用于限制一笔交易会广播给多少个对等节点。此外,你也可以通过 broadcast 配置项关闭广播。 每个区块提交后,CometBFT 都会重新检查所有未提交交易(可通过 recheck 配置项禁用),其方式是反复调用 ABCI CheckTxAsync。

交易排序

目前,交易除了按到达顺序(通过 RPC 或来自其他节点)之外,没有其他排序机制。 因此,指定顺序的唯一方法是把它们发送到同一个节点。 valA:
  • tx1
  • tx2
  • tx3
如果交易分散发送到不同节点,就无法确保它们会按预期顺序处理。 valA:
  • tx1
  • tx2
valB:
  • tx3
如果 valB 是提议者,顺序可能是:
  • tx3
  • tx1
  • tx2
如果 valA 是提议者,顺序可能是:
  • tx1
  • tx2
  • tx3
不过,如果交易内部带有某种值,例如 order/nonce/sequence number,应用可以拒绝乱序交易。因此,如果某个节点先收到 tx3,再收到 tx1,它可以先拒绝 tx3,再接受 tx1。发送方随后可以重试发送 tx3,而在该节点见到 tx2 之前,tx3 很可能仍会被拒绝。

2. Nop

nop(no operation 的缩写)mempool 用于 ABCI 应用开发者希望自行构建 mempool 的场景。当 type = "nop" 时,交易不会存储在任何地方,也不会通过 P2P 网络 gossip 给其他对等节点。 通过现有 RPC 方法提交交易(BroadcastTxSync、BroadcastTxAsync 和 BroadcastTxCommit)将始终返回错误。 由于共识层无法知道是否有可供提交的交易,节点仍然会持续创建区块,因此区块有时可能为空。在这种情况下,禁止使用 consensus.create_empty_blocks=false。 ABCI 应用需要负责通过 PrepareProposal 存储、传播和提议交易。具体设计由 ABCI 应用开发者自行决定。

3. App

CometBFT 的 app mempool 与 Cosmos SDK 的 application mempool 不同。SDK 的 application mempool 控制区块提议阶段的交易排序。CometBFT 的 app mempool 会把整个交易生命周期(存储、gossip 和重新检查)从 CometBFT 委托给应用。CometBFT 的 app mempool 当前已在 Cosmos EVM 中实现。
app mempool(也称为 Krakatoa mempool)适用于 ABCI 应用希望在 flood 与 nop mempool 之间取得折中的场景。 app mempool 将交易存储、验证和重新检查完全委托给 ABCI 应用。CometBFT 充当一个轻量代理:从 RPC 和 P2P 接收交易,通过 ABCI 转发给应用,并将应用收割出来的交易广播给对等节点。

设计动机

传统的 flood mempool 架构存在若干限制: ABCI 锁争用:在 flood mempool 中,CheckTx 调用会持有 ABCI 连接锁。这个锁与 PrepareProposal 和 FinalizeBlock 等共识关键操作共享。由于 CheckTx 的调用量与网络负载直接成正比,并且完全由外部提交交易的行为驱动,这意味着受外部影响的工作负载可能阻塞区块构建与最终确认。在已提交区块之后进行重新检查时,问题会进一步放大:所有新进入的交易和共识操作都必须等待完整的重新检查结束。 应用控制能力有限:应用无法控制何时进行重新检查、在重新检查期间如何确定交易优先级,以及 mempool 如何与区块构建交互。整个生命周期都由 CometBFT 驱动。 冗余的状态管理:CometBFT 维护自己的交易存储(并发链表),而应用通常还需要自己的 mempool 来进行排序和优先级控制。这会导致状态重复以及同步开销。 app mempool 通过让应用成为 mempool 状态的唯一真实来源来消除这些问题。CometBFT 不再为 mempool 操作持有 ABCI 锁:InsertTx 和 ReapTxs 会并发调用,而应用需要自行负责内部同步。

快速开始

要启用 Krakatoa app mempool,请在 CometBFT 的 config.toml 中设置 mempool 类型:
[mempool]
type = "app"
这会将 CometBFT 从默认的 flood mempool 切换到应用委托模型。CometBFT 会通过 InsertTx 将交易转发给你的应用,并通过 ReapTxs 拉取已验证交易,而不是自行管理 mempool 状态。 你的应用必须实现 InsertTx 和 ReapTxs 这两个 ABCI handler。

新增 ABCI 方法

作为 mempool 连接的一部分,ABCI Application 接口新增了两个方法。

InsertTx

service ABCIApplication {
  rpc InsertTx(RequestInsertTx) returns (ResponseInsertTx);
}

message RequestInsertTx {
  bytes tx = 1;
}

message ResponseInsertTx {
  uint32 code = 1;
}
当 CometBFT 收到一笔交易时,无论来自 RPC 客户端(BroadcastTxSync、BroadcastTxAsync)还是来自对等节点的 P2P gossip,都会调用 InsertTx。应用应负责验证该交易,并将其存储到自己的 mempool 中。 响应码:
代码含义CometBFT 行为
0 (OK)交易已接受交易会被标记为已见过,且不会再次插入
1 - 31,999交易被拒绝交易会被标记为已见过,且不会重试
>= 32,000 (Retry)临时拒绝交易会从已见缓存中移除,以便稍后重试
当应用的 mempool 暂时达到容量上限时,重试机制会很有用。通过返回重试码,应用表明该交易并非天然无效,只是当前无法接收。当该交易再次被收到时(来自对等节点,或通过 RPC 再次提交),它会再次被转发给应用。 并发保证:从 CometBFT 的角度看,InsertTx 调用是线程安全的。多个 goroutine 可能会并发调用 InsertTx(例如,不同对等节点同时到达的交易)。应用需要自行负责内部同步。 无 ABCI 锁:与 flood mempool 中的 CheckTx 不同,InsertTx 不会持有 ABCI 连接锁。这意味着 InsertTx 调用不会阻塞共识操作,而共识操作也不会阻塞 InsertTx。

ReapTxs

service ABCIApplication {
  rpc ReapTxs(RequestReapTxs) returns (ResponseReapTxs);
}

message RequestReapTxs {
  uint64 max_bytes = 1;
  uint64 max_gas   = 2;
}

message ResponseReapTxs {
  repeated bytes txs = 1;
}
ReapTxs 由 AppReactor 周期性调用,用于从应用获取新的、已验证的交易,以便进行 p2p 广播。应用应返回那些已经可以 gossip 的交易,通常是已经通过验证且有资格被打包进区块的交易。 当 max_bytes 和 max_gas 都为零时,应用应返回所有可用交易,不受限制。

AppMempool

AppMempool 是 CometBFT 侧的实现,它满足 Mempool 接口,但将所有实际工作委托给应用。

AppMempool 会做什么

  • 通过 InsertTx 将传入交易代理给应用
  • 维护一个已见缓存(LRU,10 万条目),避免重复交易被再次插入
  • 在转发之前根据 max_tx_bytes 验证交易大小
  • 通过将可重试交易从已见缓存中移除来处理重试语义

AppMempool 不会做什么

  • 存储交易,所有 mempool 状态都归应用所有
  • 在区块后调用 Update,重新检查由应用负责
  • 为 ReapMaxBytesMaxGas 提供交易,它始终返回 nil,因为应用通过 PrepareProposal 构建区块

AppReactor

AppReactor 取代了传统的 mempool Reactor,负责 P2P 交易 gossip。

广播

该 reactor 运行一个后台循环,执行以下操作:
  1. 每隔 reap_interval 调用一次应用的 ReapTxs(默认 500ms)。
  2. 将返回的交易按批次分块(每批最多 MaxBatchBytes)。
  3. 将每个批次广播给所有已连接的对等节点。

接收

当某个对等节点发送交易时,reactor 会:
  1. 从 P2P envelope 中反序列化交易批次
  2. 对每笔交易调用 AppMempool 的 InsertTx
  3. 记录日志并丢弃插入失败的交易(已见过、过大,或被应用拒绝)

支持 BroadcastTx... 方法

为支持现有链以及 CometBFT 的 tx 广播 RPC 方法,在使用 app mempool 时,仍然保留了对 BroadcastTxSync、BroadcastTxAsync 和 BroadcastTxCommit 的兼容性。 通过这些 RPC 接收的交易会在热路径中调用 CheckTx,且不会持有 ABCI 连接锁。如果 ABCI 应用希望支持这些方法,就必须在应用中接入 CheckTxHandler,并自行处理它与其他 ABCI 状态变更之间的加锁关系。 强烈建议应用在使用 app mempool 时不要使用这些方法。应用应实现应用侧 RPC 方法来接收 tx,并将这些 tx 直接插入其 app mempool 实现(或其他等效数据结构)中,仅依赖 CometBFT 告知它们哪些交易是通过 p2p 网络收到的。

交易生命周期

使用 app mempool 后,交易生命周期会发生明显变化:

之前的生命周期(flood mempool)

  1. 交易通过 RPC 或 P2P 到达
  2. CometBFT 验证大小并检查已见缓存
  3. CometBFT 对应用调用 CheckTx(持有 ABCI 锁)
  4. 如果有效,CometBFT 将交易存储到其链表中
  5. CometBFT 将交易广播给所有对等节点
  6. 在区块提议阶段,CometBFT 调用 ReapMaxBytesMaxGas,并将交易传给 PrepareProposal
  7. 区块提交后,CometBFT 通过 CheckTx 重新检查所有剩余交易(整个重新检查期间都持有 ABCI 锁)

更新后的 P2P 生命周期(app mempool)

  1. 交易通过 P2P 到达
  2. CometBFT 验证大小并检查已见缓存
  3. CometBFT 对应用调用 InsertTx(不持有 ABCI 锁)
  4. 应用验证该交易,并将其存入自己的 mempool
  5. AppReactor 周期性调用 ReapTxs,并将返回的交易广播给对等节点
  6. 在区块提议阶段,PrepareProposal 不会从 CometBFT 收到任何交易,区块由应用基于自己的 mempool 构建
  7. 区块提交后,应用按照自己的调度运行自己的重新检查逻辑

更新后的应用侧 RPC 生命周期(app mempool)

  1. 交易通过应用侧 RPC 到达应用
  2. 应用验证并将该 tx 插入其 app mempool 实现。注意,验证也可以在插入之后进行,这由应用自行决定
  3. 一旦交易通过验证,应用会通过 ReapTxs ABCI 方法返回该 tx 的字节,从而将其提供给 CometBFT
  4. CometBFT 将这个已验证交易 gossip 给对等节点。
  5. 在区块提议阶段,PrepareProposal 不会从 CometBFT 收到任何交易:区块由应用基于自己的 mempool 构建
  6. 区块提交后,应用按照自己的调度运行自己的重新检查逻辑

更新后的 broadcast_tx_... RPC 生命周期(app mempool)

  1. 交易通过 RPC 到达
  2. CometBFT 验证大小并检查已见缓存
  3. CometBFT 对应用调用 CheckTx(不持有 ABCI 锁,此处如需加锁由应用自行负责)。这里使用 CheckTx 是为了保持与现有客户端的 API 兼容性。实现了应用侧 mempool 的应用应认真考虑实现自己的应用侧 RPC 方法,直接处理交易接入,而不是依赖 CometBFT 的这些方法
  4. 应用验证并将该交易存入自己的 mempool
  5. AppReactor 周期性调用 ReapTxs,并将返回的交易广播给对等节点
  6. 在区块提议阶段,PrepareProposal 不会从 CometBFT 收到任何交易,区块由应用基于自己的 mempool 构建
  7. 区块提交后,应用按照自己的调度运行自己的重新检查逻辑

区块构建

由于 AppMempool 在 ReapMaxBytesMaxGas 上返回 nil,区块执行器不会向 PrepareProposal 传递任何 mempool 交易。应用的 PrepareProposalHandler 应直接从自己的 mempool 中选择交易。 这使应用能够完全控制交易排序、优先级和是否纳入区块。

应用的保证与职责

在实现 InsertTx 和 ReapTxs 时,应用应了解以下事项:

CometBFT 对应用的保证

  • InsertTx 不会以空交易作为参数调用
  • InsertTx 不会以超过 max_tx_bytes 的交易作为参数调用
  • 返回重试码的交易会从已见缓存中移除,并且之后可能再次提交
  • 无论是否有新交易到达,ReapTxs 都会被周期性调用(每 500ms 一次)
  • InsertTx 和 ReapTxs 都不会持有 ABCI 连接锁

应用的职责

  • 并发:应用必须安全地处理并发 InsertTx 调用
  • 重新检查:应用必须(可选)在区块提交后实现自己的交易重新验证逻辑
  • 区块构建:应用必须在其 PrepareProposalHandler 中为区块选择交易,CometBFT 不会提供 mempool 交易
  • 存储:应用必须管理自己的交易存储与淘汰

配置

要启用 app mempool,请在 config.toml 中设置 mempool 类型:
[mempool]
type = "app"
以下选项适用于 app mempool:

共享选项

这些选项与其他 mempool 类型共享:
选项说明默认值
max_tx_bytes单笔交易的最大大小(在 InsertTx 之前检查)1048576 (1 MB)
max_batch_bytes单次广播批次的最大大小0(无限制)
broadcast启用或禁用 P2P 交易广播true

App mempool 选项

这些选项仅在 type = "app" 时生效:
选项说明默认值
seen_cache_size用于对已见交易去重的 LRU 缓存大小。可防止已经转发给应用的交易被再次插入。100000
check_tx_retry_delay交易通过 CheckTx 转发给应用后,在多长时间之后从已见缓存中移除。如果返回不可重试错误码,则会在完整延迟后再从缓存中移除(从而允许后续重试)。如果返回可重试错误码,则只使用 1/10 的延迟。"500ms"
reap_max_bytes告知应用每次调用 ReapTxs 最多应返回多少字节。0 表示无限制。0
reap_max_gas告知应用每次调用 ReapTxs 最多应返回多少 gas。0 表示无限制。0
reap_intervalReapTxs 调用之间的间隔。"500ms"

示例

[mempool]
type = "app"
max_tx_bytes = 1048576

# App mempool options
seen_cache_size = 100000
reap_max_bytes = 0
reap_max_gas = 0
reap_interval = "500ms"
check_tx_retry_delay = "500ms"
当使用 app mempool 时,flood mempool 特有的选项(size、max_txs_bytes、cache_size、recheck 等)不会生效。

相关文档


A mempool (a contraction of memory and pool) is a node’s data structure for storing information on uncommitted transactions. It acts as a sort of waiting room for transactions that have not yet been committed. CometBFT currently supports three types of mempools: flood, nop, and app.

1. Flood

The flood mempool stores transactions in a concurrent linked list. When a new transaction is received, it first checks if there’s space for it (size and max_txs_bytes config options) and that it’s not too big (max_tx_bytes config option). Then, it checks if this transaction has already been seen before by using an LRU cache (cache_size regulates the cache’s size). If all checks pass and the transaction is not in the cache (meaning it’s new), the ABCI CheckTxAsync method is called. The ABCI application validates the transaction using its own rules. If the transaction is deemed valid by the ABCI application, it’s added to the linked list. The mempool’s name (flood) comes from the dissemination mechanism. When a new transaction is added to the linked list, the mempool sends it to all connected peers. Peers themselves gossip this transaction to their peers and so on. One can say that each transaction “floods” the network, hence the name flood. Note there are experimental config options experimental_max_gossip_connections_to_persistent_peers and experimental_max_gossip_connections_to_non_persistent_peers to limit the number of peers a transaction is broadcast to. Also, you can turn off broadcasting with the broadcast config option. After each committed block, CometBFT rechecks all uncommitted transactions (can be disabled with the recheck config option) by repeatedly calling the ABCI CheckTxAsync.

Transaction ordering

Currently, there’s no ordering of transactions other than the order they’ve arrived (via RPC or from other nodes). So the only way to specify the order is to send them to a single node. valA:
  • tx1
  • tx2
  • tx3
If the transactions are split up across different nodes, there’s no way to ensure they are processed in the expected order. valA:
  • tx1
  • tx2
valB:
  • tx3
If valB is the proposer, the order might be:
  • tx3
  • tx1
  • tx2
If valA is the proposer, the order might be:
  • tx1
  • tx2
  • tx3
That said, if the transactions contain some internal value, like an order/nonce/sequence number, the application can reject transactions that are out of order. So if a node receives tx3, then tx1, it can reject tx3 and then accept tx1. The sender can then retry sending tx3, which should probably be rejected until the node has seen tx2.

2. Nop

The nop (short for no operation) mempool is used when the ABCI application developer wants to build their own mempool. When type = "nop", transactions are not stored anywhere and are not gossiped to other peers using the P2P network. Submitting a transaction via the existing RPC methods (BroadcastTxSync, BroadcastTxAsync, and BroadcastTxCommit) will always result in an error. Because there’s no way for the consensus to know if transactions are available to be committed, the node will always create blocks, which can be empty sometimes. Using consensus.create_empty_blocks=false is prohibited in such cases. The ABCI application becomes responsible for storing, disseminating, and proposing transactions using PrepareProposal. The concrete design is up to the ABCI application developers.

3. App

The CometBFT app mempool is distinct from the Cosmos SDK’s application mempool. The SDK’s application mempool controls transaction ordering at block proposal time. The CometBFT app mempool delegates the entire transaction lifecycle (storage, gossip, and rechecking) from CometBFT to the application. The CometBFT app mempool is currently implemented in Cosmos EVM.
The app mempool (also known as the Krakatoa mempool) is used when the ABCI application wants a middle ground between the flood and nop mempool. The app mempool delegates transaction storage, validation, and rechecking entirely to the ABCI application. CometBFT acts as a thin proxy — receiving transactions from RPC and P2P, forwarding them to the application via ABCI, and broadcasting application reaped transactions to peers.

Motivation

The traditional flood mempool architecture has several limitations: ABCI lock contention: In the flood mempool, CheckTx calls hold the ABCI connection lock. This lock is shared with consensus-critical operations like PrepareProposal and FinalizeBlock. Since CheckTx volume is directly proportional to network load and fully driven by external actors submitting transactions, this means an externally influenced workload can hold up block building and finalization. During rechecking after a committed block, the problem compounds — all incoming transactions and consensus operations must wait for the full recheck pass to complete. Limited application control: The application has no control over when rechecking occurs, how transactions are prioritized during recheck, or how the mempool interacts with block building. CometBFT drives the entire lifecycle. Redundant state management: CometBFT maintains its own transaction storage (the concurrent linked list) even though the application often needs its own mempool for ordering and prioritization. This leads to duplicated state and synchronization overhead. The app mempool eliminates these issues by making the application the single source of truth for mempool state. CometBFT no longer holds the ABCI lock for mempool operations: InsertTx and ReapTxs are called concurrently and the application is responsible for its own synchronization.

Quick Start

To enable the Krakatoa app mempool, set the mempool type in your CometBFT config.toml:
[mempool]
type = "app"
This switches CometBFT from the default flood mempool to the application delegated model. CometBFT will forward transactions to your application via InsertTx and pull validated transactions back via ReapTxs, rather than managing mempool state itself. Your application must implement the InsertTx and ReapTxs ABCI handlers.

New ABCI Methods

Two new methods are added to the ABCI Application interface as part of the mempool connection:

InsertTx

service ABCIApplication {
  rpc InsertTx(RequestInsertTx) returns (ResponseInsertTx);
}

message RequestInsertTx {
  bytes tx = 1;
}

message ResponseInsertTx {
  uint32 code = 1;
}
InsertTx is called when CometBFT receives a transaction, either from an RPC client (BroadcastTxSync, BroadcastTxAsync) or from a peer via P2P gossip. The application is expected to validate and store the transaction in its own mempool. Response codes:
CodeMeaningCometBFT Behavior
0 (OK)Transaction acceptedTransaction is marked as seen and will not be re-inserted
1 - 31,999Transaction rejectedTransaction is marked as seen and will not be retried
>= 32,000 (Retry)Temporary rejectionTransaction is removed from the seen cache so it can be retried later
The retry mechanism is useful when the application’s mempool is temporarily at capacity. By returning a retry code, the application signals that the transaction is not inherently invalid — it simply cannot be accepted right now. When the transaction is received again (from a peer or resubmitted via RPC), it will be forwarded to the application again. Concurrency guarantee: InsertTx calls are thread-safe from CometBFT’s perspective. Multiple goroutines may call InsertTx concurrently (e.g., transactions arriving from different peers simultaneously). The application is responsible for its own internal synchronization. No ABCI lock: Unlike CheckTx in the flood mempool, InsertTx does not hold the ABCI connection lock. This means InsertTx calls do not block consensus operations, and consensus operations do not block InsertTx.

ReapTxs

service ABCIApplication {
  rpc ReapTxs(RequestReapTxs) returns (ResponseReapTxs);
}

message RequestReapTxs {
  uint64 max_bytes = 1;
  uint64 max_gas   = 2;
}

message ResponseReapTxs {
  repeated bytes txs = 1;
}
ReapTxs is called periodically by the AppReactor to retrieve new, validated transactions from the application for p2p broadcast. The application should return transactions that are ready for gossip — typically transactions that have been validated and are eligible for block inclusion. When max_bytes and max_gas are both zero, the application should return all available transactions without limits.

AppMempool

The AppMempool is the CometBFT side implementation that fulfills the Mempool interface while delegating all real work to the application.

What AppMempool does

  • Proxies incoming transactions to the application via InsertTx
  • Maintains a seen cache (LRU, 100k entries) to avoid re-inserting duplicate transactions
  • Validates transaction size against max_tx_bytes before forwarding
  • Handles retry semantics by removing retryable transactions from the seen cache

What AppMempool does NOT do

  • Store transactions — the application owns all mempool state
  • Call Update after blocks — rechecking is the application’s responsibility
  • Provide transactions for ReapMaxBytesMaxGas — always returns nil, since the application builds blocks via PrepareProposal

AppReactor

The AppReactor replaces the traditional mempool Reactor for P2P transaction gossip.

Broadcasting

The reactor runs a background loop that:
  1. Calls ReapTxs on the application every reap_interval duration (default 500ms).
  2. Chunks the returned transactions into batches (up to MaxBatchBytes)
  3. Broadcasts each batch to all connected peers

Receiving

When a peer sends transactions, the reactor:
  1. Deserializes the transaction batch from the P2P envelope
  2. Calls InsertTx on the AppMempool for each transaction
  3. Logs and discards transactions that fail insertion (already seen, too large, or rejected by the application)

Supporting BroadcastTx... methods

In an effort to support existing chains and CometBFT tx broadcast RPC methods, compatibility with BroadcastTxSync, BroadcastTxAsync, and BroadcastTxCommit has been maintained when using the app mempool. Transactions ingested via these RPC call CheckTx in the hot path without the ABCI connection lock. If the ABCI application would like to support these methods, they must wire a CheckTxHandler into their application and manage the locking relative to other ABCI state changes themselves. It is highly recommended for applications to not use these methods when using an app mempool. Applications should implement application side RPC methods for tx ingestion and insert these txs directly into their app mempool implementation (or other comparable data structure), only relying on CometBFT to inform them of transactions that are received over the p2p network.

Transaction Lifecycle

With the app mempool, the transaction lifecycle changes significantly:

Previous Lifecycle (flood mempool)

  1. Transaction arrives via RPC or P2P
  2. CometBFT validates size and checks the seen cache
  3. CometBFT calls CheckTx on the application (holds ABCI lock)
  4. If valid, CometBFT stores the transaction in its linked list
  5. CometBFT broadcasts the transaction to all peers
  6. At block proposal time, CometBFT calls ReapMaxBytesMaxGas and passes transactions to PrepareProposal
  7. After block commit, CometBFT rechecks all remaining transactions via CheckTx (holds ABCI lock for the entire recheck)

Updated P2P Lifecycle (app mempool)

  1. Transaction arrives via P2P
  2. CometBFT validates size and checks the seen cache
  3. CometBFT calls InsertTx on the application (no ABCI lock)
  4. The application validates and stores the transaction in its own mempool
  5. The AppReactor periodically calls ReapTxs and broadcasts returned transactions to peers
  6. At block proposal time, PrepareProposal receives no transactions from CometBFT — the application builds the block from its own mempool
  7. After block commit, the application runs its own recheck logic on its own schedule

Updated application RPC Lifecycle (app mempool)

  1. Transaction arrives to application via application side RPC
  2. Application validates and inserts the tx into its app mempool implementation. Note that validating can happen after insert, it’s up to the application!
  3. The application provides the tx to CometBFT once validated by returning its bytes via the ReapTxs ABCI method.
  4. CometBFT gossips the validated transaction to peers.
  5. At block proposal time, PrepareProposal receives no transactions from CometBFT: the application builds the block from its own mempool
  6. After block commit, the application runs its own recheck logic on its own schedule

Updated (broadcast_tx_...) RPC Lifecycle (app mempool)

  1. Transaction arrives via RPC
  2. CometBFT validates size and checks the seen cache
  3. CometBFT calls CheckTx on the application (no ABCI lock, it is up to the application to perform any necessary locking here). CheckTx is used here to maintain API compatibility with existing clients. Applications implementing their own application side mempool should strongly consider implementing their own application side RPC methods to directly handle transaction ingestion, rather than relying on CometBFT’s.
  4. The application validates and stores the transaction in its own mempool
  5. The AppReactor periodically calls ReapTxs and broadcasts returned transactions to peers
  6. At block proposal time, PrepareProposal receives no transactions from CometBFT — the application builds the block from its own mempool
  7. After block commit, the application runs its own recheck logic on its own schedule

Block Building

Since the AppMempool returns nil from ReapMaxBytesMaxGas, the block executor passes no mempool transactions to PrepareProposal. The application’s PrepareProposalHandler is expected to select transactions directly from its own mempool. This gives the application full control over transaction ordering, prioritization, and inclusion.

Application Guarantees and Responsibilities

When implementing InsertTx and ReapTxs, applications should be aware of the following:

CometBFT guarantees to the application

  • InsertTx will not be called with empty transactions
  • InsertTx will not be called with transactions exceeding max_tx_bytes
  • Transactions returning a retry code will be removed from the seen cache and may be re-submitted
  • ReapTxs will be called periodically (every 500ms) regardless of whether new transactions have arrived
  • InsertTx and ReapTxs will not hold the ABCI connection lock

Application responsibilities

  • Concurrency: The application must handle concurrent InsertTx calls safely
  • Rechecking: The application must (optionally) implement its own transaction revalidation after blocks are committed
  • Block building: The application must select transactions for blocks in its PrepareProposalHandler — CometBFT will not provide mempool transactions
  • Storage: The application must manage its own transaction storage and eviction

Configuration

To enable the app mempool, set the mempool type in config.toml:
[mempool]
type = "app"
The following options apply to the app mempool:

Shared options

These options are shared with other mempool types:
OptionDescriptionDefault
max_tx_bytesMaximum size of a single transaction (checked before InsertTx)1048576 (1 MB)
max_batch_bytesMaximum size of a broadcast batch0 (no limit)
broadcastEnable or disable P2P transaction broadcastingtrue

App mempool options

These options only apply when type = "app":
OptionDescriptionDefault
seen_cache_sizeSize of the LRU cache for deduplicating seen transactions. Prevents re-inserting transactions already forwarded to the application.100000
check_tx_retry_delayDelay after which a tx is removed from the seen cache after forwarding to the application via CheckTx. If a non-retryable error code is returned, the full delay is used before removing from the cache (allowing a retry). If a retryable error code is returned, 1/10th of the delay is used."500ms"
reap_max_bytesInforms the application the maximum amount of bytes it should return from each call to ReapTxs. 0 means no limit.0
reap_max_gasInforms the application the maximum amount of gas it should return from each call to ReapTxs. 0 means no limit.0
reap_intervalInterval between ReapTxs calls."500ms"

Example

[mempool]
type = "app"
max_tx_bytes = 1048576

# App mempool options
seen_cache_size = 100000
reap_max_bytes = 0
reap_max_gas = 0
reap_interval = "500ms"
check_tx_retry_delay = "500ms"
Options specific to the flood mempool (size, max_txs_bytes, cache_size, recheck, etc.) have no effect when using the app mempool.