- 2025-09-04: Initial draft
Proposed
The CAT mempool previously ordered transactions strictly based on priority (fee / gas). This is incongruent with the replay protection mechanism which depends on the order of transactions by a signer having a monotonically increasing sequence number. This becomes more apparent with multiple transactions by the same signer in the same block.
We need a mempool ordering algorithm that respects per-signer sequencing constraints while still maximising revenue through choosing the highest paid tranasctions (per byte or gas used).
Group transactions by signer into signer-bundled sets ("tx sets") and order based on average weighted priority per set. Within tx set order transactions in ascending sequence order. Block building and recheckTx iterates through transactions in those order. This will preserve sequence ordering per signer while maximising tx fees per block.
Key aspects:
- Signer-bundled sets: A
txSetholds transactions from a single signer. The set tracks:txs: maintained in ascending sequence order; ties broken by earlier arrival timestamp.aggregatedPriority: gas-weighted average of member transaction priorities, floored toint64.bytes,firstTimestamp, and running accumulatorstotalGasWantedandweightedPrioritySum.
- Global ordering of sets:
orderedTxSetsis kept sorted by(aggregatedPriority desc, firstTimestamp asc). This preserves FIFO between sets of equal aggregated priority. - Within-set ordering: Transactions are iterated in ascending sequence, guaranteeing that a later sequence from the same signer is never submitted before an earlier one.
- Eviction policy when full: Compute the new set aggregated priority if the incoming transaction were added (
aggregatedPriorityAfterAdd). Identify victim sets with aggregated priority strictly below the incoming set. Evict transactions from victim sets starting from the end (highest sequence numbers first) until enough bytes are freed. If insufficient bytes can be freed, reject the incoming transaction. - TTL purge: Expiration by height/time removes transactions from sets and reorders or removes empty sets accordingly.
-
Data structures (in
mempool/cat/store.go):txSet:signerKey []byteandsigner []bytelabelstxs []*wrappedTxkept in ascendingsequenceorder; equal sequences ordered by earliertimestamp.aggregatedPriority int64bytes int64firstTimestamp time.TimetotalGasWanted int64,weightedPrioritySum int64
store:setsBySigner map[string]*txSetorderedTxSets []*txSet(sorted by weighted average priority)- transaction index, byte accounting, reservation tracking
-
Aggregation:
- On insert: update aggregated priority
totalGasWanted += gasWanted,weightedPrioritySum += priority * gasWanted, thenaggregatedPriority = weightedPrioritySum / totalGasWanted(integer division). Aggregated priority is relative to the amount of gasWanted per transaction. Transactions with more gasWanted will have larger weighting. Alternatively we could have used transaction size. - On remove: reverse the updates and recompute
aggregatedPriority(or zero if empty).
- On insert: update aggregated priority
-
Ordering:
- Global:
orderedTxSetsuses binary search insertion to maintain ordering by(aggregatedPriority desc, firstTimestamp asc). - Intra-set:
addTxToSetuses binary search by(sequence asc, timestamp asc). - Iteration: block production reaping and recheckTx iterates sets in
orderedTxSetsorder and then iteratestxsper set.
- Global:
-
Eviction:
- When full, remove lowest txSets starting from the latest sequence and moving towards an earlier sequence
-
TTL purge:
- TTL will remove an entire transaction set (i.e. everything from a specific signer) since all following transactions would also become invalidated
- Within a signer, transactions are never emitted out of ascending sequence order.
- Across signers, ordering is by set weighted average priority (desc), with earlier
firstTimestampbreaking ties (FIFO between equal-priority sets). - Weighted average priority updates are consistent with gas-weighted means as transactions are added/removed.
- Eviction never removes lower sequence numbers in a set before removing higher ones, preserving the ability to submit remaining sequences correctly.
-
Internal store API:
- Added:
getOrderedTxs()returns a copy of all transactions in correct emitted order. - Added:
aggregatedPriorityAfterAdd(*wrappedTx) int64to predict set priority with a candidate tx. - Added/Renamed:
getTxSetsBelowPriority(priority int64) ([]*txSet, int64)returns sets with aggregated priority below the threshold and their cumulative bytes. Used for evicting lower paying transactions. - Removed legacy per-tx ordered list APIs (
deleteOrderedTx, etc.).
- Added:
-
TxPool eviction path now operates on signer sets using aggregated priority and evicts from the tail of victim sets.
External mempool interface (CometBFT mempool.Mempool) remains unchanged.
Rather than replace by fee, users can improve the likeness of their transaction landing by increasing the gas price of later transactions, thus raising the aggregated priority. Given sequence awareness, a replace by fee protocol may easily be supported in the future.
Unit tests were added/updated in mempool/cat:
- Priority:
TestAggregatedPriorityWeightedByGasTestAggregatedPriorityAfterAdd
- Ordering:
TestIntraSetOrderingBySequenceThenTimestampTestInterSetOrderingByAggregatedPriorityAndTimestamp
- Eviction and reaping semantics validated via
TestTxPool_Evictionand existing reaping tests, adjusted to set-based eviction. - TTL purging validated to keep ordering and aggregation consistent after removals.
- External RPC and mempool interfaces remain unchanged.
- No changes to ABCI interface (although signer and sequence need to be populated in order for this to work)
- Internal store test helpers and function names changed (e.g.,
getTxSetsBelowPriority); tests were updated accordingly.