fix(mempool): honor sender_bucket / timeline_id / count / before in read_timeline#719
fix(mempool): honor sender_bucket / timeline_id / count / before in read_timeline#719keanji-x wants to merge 1 commit into
Conversation
…ead_timeline The previous CoreMempool::read_timeline ignored every routing parameter and used a single global TxnCache to dedup across consecutive ticks. That meant the first read_timeline call of a tick drained every broadcastable txn from the reth pool, populated the cache, and every subsequent call (other buckets, Failover priority, even other peers) saw an empty result until TTL/capacity cleared the cache. Effects: - The sender_bucket-based fan-out across PFNs collapsed: whichever peer iterated first this tick received the entire pool, the others got nothing for ~60s. - The Failover delay (`shared_mempool_failover_delay_ms`) was bypassed, since `before` was ignored. - The `shared_mempool_batch_size` cap was bypassed: a tick could push the whole pool into a single broadcast batch. - The returned MultiBucketTimelineIndexIds used `id_per_bucket` length equal to the txn count rather than the number of fee buckets, so the peer-side resume state never advanced meaningfully. Replace TxnCache with a BroadcastIndex that lazily mirrors the reth pool's broadcastable set and assigns each txn a monotonic timeline_id within its sender_bucket on first observation. read_timeline now: - Returns only txns whose sender hashes into the requested sender_bucket (last-byte-mod-N, matching upstream Aptos's `transaction_store::sender_bucket`). - Resumes after the input timeline_id (max position; we don't subdivide by fee bucket). - Caps the batch at `count`. - Skips txns first observed after `before` (Failover delay gate), and breaks on the first too-fresh entry since timeline_id is monotonic with insertion time. - Returns a MultiBucketTimelineIndexIds whose length matches the input so PeerSyncState stays well-formed. GC: entries are dropped when their txn no longer appears in the snapshot from the upstream pool (committed, expired, or evicted). Per-(sender_bucket, fee_bucket) subdivision and accurate ready_time emission are intentional follow-ups: both require richer txn metadata from the TxPool trait (gas price for fee buckets, SystemTime for ready_time), which is out of scope for this fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Tracing through the implementation with the broadcast design notes — the cursor restoration itself (sender_bucket sharding + per-(peer, bucket) cursor + 1. No retransmit path → fault-tolerance regression vs
|
Summary
CoreMempool::read_timelinewas ignoring every routing parameter (sender_bucket,timeline_id,count,before,priority_of_receiver) and using a single globalTxnCacheto dedup across consecutive ticks. The result: the first call of any tick drained every broadcastable txn from the reth pool into one batch, and every subsequent call (other buckets, Failover priority, even other peers) returned empty until the cache's TTL/capacity expired ~60s later.Observable bugs
shared_mempool_failover_delay_ms) bypassed.beforewas ignored, so Failover peers received txns with the same timing as Primary.shared_mempool_batch_sizecap bypassed. A single broadcast batch could contain the entire pool.MultiBucketTimelineIndexIds.id_per_buckethad length equal to the txn count rather than the number of fee buckets, soPeerSyncState::timelinesnever advanced meaningfully.Fix
Replace
TxnCachewith aBroadcastIndexthat lazily mirrors the reth pool's broadcastable set, assigning each txn a monotonictimeline_idwithin itssender_bucketon first observation.read_timelinenow:sender_bucket(last-byte-mod-N, matchingtransaction_store::sender_bucketin upstream Aptos).timeline_id(max across positions; this PR does not subdivide by fee bucket internally — see follow-ups).count.beforeand breaks the scan on the first too-fresh entry (timeline_id is monotonic with insertion time, so the tail is also too fresh).MultiBucketTimelineIndexIdswhose length matches the caller's input soPeerSyncStatestays well-formed.GC: entries are dropped when their txn no longer appears in the snapshot from the upstream pool (committed, expired, or evicted).
Follow-ups (intentionally out of scope)
TxPool::get_broadcast_txns. Current PR collapses all fee buckets into one timeline, which loses high-fee-first ordering within a broadcast batch but matches the reth pool's natural ordering.ready_timeemission. NeedsSystemTimetracking in addition toInstant. Left at0to match prior behavior.timeline_rangeretransmit path. Still returns empty (unchanged from before); orthogonal to this fix.Test plan
cargo check -p aptos-mempool— passescargo test -p aptos-mempool --lib— 4 new unit tests pass:sender_bucket_takes_last_byte_mod_n(sender_bucket arithmetic)broadcast_index_partitions_by_sender_bucket(bucket partitioning)range_query_respects_resume_id(BTreeMap::rangeresume semantics)failover_before_filter_breaks_scan(before-driven scan break)shared_mempool_failover_delay_msis honored on Failover peers (Failover broadcasts strictly lag Primary by configured delay)🤖 Generated with Claude Code