Skip to content

feat(mempool): poll-reconcile single timeline for broadcast (replaces TTL TxnCache) - #808

Open
nekomoto911 wants to merge 7 commits into
Galxe:mainfrom
nekomoto911:fix/mempool-read-timeline-cursor-stub
Open

feat(mempool): poll-reconcile single timeline for broadcast (replaces TTL TxnCache)#808
nekomoto911 wants to merge 7 commits into
Galxe:mainfrom
nekomoto911:fix/mempool-read-timeline-cursor-stub

Conversation

@nekomoto911

@nekomoto911 nekomoto911 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

Replace architecture-A mempool broadcast progress (global TxnCache + TTL four-state + empty cursor stub) with poll-reconcile + per-sender_bucket monotonic timeline, so gaptos shared-mempool can use real read_timeline cursors and timeline_range* for ACK / in-flight / retransmit.

Ground truth remains TxPool::get_broadcast_txns (periodic reconcile). Packing path (get_batchbest_txns) is unchanged. No gaptos network.rs edits.

Also supersedes the earlier audit#1090 empty-cursor-only approach on this branch: instead of leaving timeline unimplemented, we implement a single logical timeline per sender bucket with fee-slot-shaped cursors (id_per_bucket.len() == broadcast_buckets.len(), progress only in slot 0).

Issue Number: closes Galxe/gravity-audit#1090 (cursor contract); broader progress-model change beyond the stub fix

How Has This Been Tested?

Unit (aptos-mempool lib)

  • Reconcile: admit monotonic ids, remove left hashes, stable id while present, re-enter new id
  • read_timeline: fee-slot-shaped cursor, incremental cursor, count truncation, empty batch no advance, before Instant filter, bucket isolation
  • timeline_range / timeline_range_of_message: MessageId window re-materialize, leave-then-range, multi-bucket flatten
  • Existing get_batch byte-budget test kept
# package name is ambiguous in workspace; use path package or existing test binary
cargo test -p 'path+file:///…/aptos-core/mempool#aptos-mempool@0.1.0' --lib
# 14 passed

E2E

  • ./gravity_e2e/run_test.sh pfn_chainPASS (Phase 0–3; black-hole p99 ~1.7–2.2s vs 18s SLA ceiling)
  • ./gravity_e2e/run_test.sh vfnPASS (3 tests)

Local notes (not in repo): acceptance checklist under _local/wiki/mempool-broadcast/ (untracked).

Key Areas to Review

  1. reconcile vs get_broadcast_txns — leave/admit order, stable id while hash stays, re-enter new id + Instant; throttle MEMPOOL_SNAPSHOT_MAX_AGE_MS (default 20ms).
  2. read_timelineExcluded(cursor0), count, before (admit Instant >= t → break); cursor_from only advances [0]; empty batch does not advance.
  3. timeline_range* — fee slot 0 only; std Mutex not reentrant (timeline_range_with_index helper); missing body skip.
  4. Locking — interior Mutex<BroadcastIndex> for &self trait methods; real contention remains outer smp.mempool (same as before).
  5. Regression risk — Phase 3 e2e still documents Arch-A TTL semantics in comments; SLA still valid as functional black-hole check under new Failover/before model.

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Performance improvement
  • Refactoring
  • Dependency update
  • Documentation update
  • Tests

Which Components or Systems Does This Change Impact?

  • Validator Node
  • Consensus
  • EVM Runtime
  • Gravity SDK Core
  • CLI Tools
  • E2E Tests
  • Documentation
  • Other (specify): shared-mempool broadcast path (FullNode / VFN / PFN)

Out of scope (follow-ups)

  1. Full MultiBucket fee (ranking sub-timelines)
  2. Hash-only poll + by-hash body join
  3. reth pending listener + long reconcile interval
  4. Explicit WAN/load proof of ACK backpressure / R6 deep-pending bias

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary of commits on branch

  1. d961dd2a — empty cursor stub (audit#1090)
  2. d7e568d1 — poll-reconcile timeline index; remove TTL TxnCache
  3. a388d114 — real read_timeline (cursor / count / before)
  4. 5454c8c0 — real timeline_range* for ACK retransmit path

read_timeline was returning MultiBucketTimelineIndexIds with
id_per_bucket = vec![0; out.len()], which is the wrong shape:
length must be fee/ranking bucket count, not batch size, and
values must be max timeline_ids per bucket.

Architecture A intentionally does not implement timeline progress
(TxnCache + TTL instead). Return an empty id_per_bucket as an
honest stub so zip/update do not invent fake (0,0) ranges or
pretend fee-bucket cursors exist.

Closes Galxe/gravity-audit#1090

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d961dd2a71

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// `vec![0; out.len()]`. See gravity-audit#1090.
// When timeline is re-enabled: return per-fee-bucket max timeline_ids
// with fixed length broadcast_buckets.len().
(out, MultiBucketTimelineIndexIds { id_per_bucket: vec![] })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid collapsing every broadcast message id

In the gaptos broadcast path, determine_broadcast_batch() passes each read_timeline() cursor to MempoolMessageId::from_timeline_ids(); with an empty new_timeline_id, the old/new zip emits no ranges, so every non-empty fresh batch to a peer is sent as MempoolMessageId([]). When two broadcasts overlap for the same peer (for example, tick interval shorter than network RTT), an ACK for the first batch can remove the sent_messages entry for the later batch in process_broadcast_ack(), causing the later ACK/backoff/retry signal to be ignored and bypassing peer backpressure. Please preserve a non-colliding message id, even if real timeline progress remains stubbed.

Useful? React with 👍 / 👎.

Replace Arch-A TxnCache/Snapshot/ObservedTopology with BroadcastIndex:
per-sender_bucket monotonic TimelineIndex + bodies/hash_to_pos, throttled
maybe_reconcile from get_broadcast_txns. Stub read_timeline/timeline_range
until later tasks; keep get_batch path unchanged.
@nekomoto911 nekomoto911 changed the title fix(mempool): return empty timeline cursor stub (audit#1090) feat(mempool): poll-reconcile single timeline for broadcast (replaces TTL TxnCache) Aug 5, 2026
…Store

Rename BroadcastIndex to TransactionStore and field names to match aptos
core_mempool (transactions, timeline_index, hash_index, TimelineIndex::
timeline_id/timeline). Document each member against the aptos counterpart
and Gravity poll-reconcile differences.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83b4312787

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

);
};
out.push((txn.clone(), 0)); // ready_time_ms = 0
last_included = Some(id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't make cursor advancement final on async receiver drops

With the new real cursor, including a txn here makes the returned new_timeline_id advance, and shared-mempool advances the peer state as soon as the batch is sent; after an ACK with retry=false, future reads for that peer start after this id. That is unsafe for Gravity's receiver path because Mempool::add_txn() calls TxPool::add_external_txn(), and the real implementation in bin/gravity_node/src/mempool.rs spawns pool.add_external_transaction(...) and returns true before the insert result is known, only logging later recoverable failures such as nonce gap/low fee/local config. Reproduce by sending one broadcast to a peer whose reth pool rejects in that spawned task: the sender receives a successful ACK, keeps the txn locally, but the peer cursor is already past it so it is never offered to that peer again. Please either make receiver admission/ACK reflect the actual pool insert result or preserve a retry/TTL path for ACKed-but-not-admitted txns.

Useful? React with 👍 / 👎.

…drain, no rebroadcast

Cover T1-A/B/C (range keeps sent_messages, max_broadcasts backpressure,
expired via timeline_range), T2 (no Fresh rebroadcast after drain), T3
(R6 multi-sender cover in ceil(S/count) ticks), and T5 unit (Failover
before=now-500ms filters fresh admits). Simulates gaptos filter/pending
logic against Gravity CoreMempoolTrait without network harness.
…try, T3 scale

App-layer Instant clock sims for WAN-like ACK delay (T4-A/B/C), immediate-ACK
drain + multi-TTL no-rebroadcast (T2-B), backoff PeerNotScheduled and Retry
range path (T1-D/E), larger R6 cover (T3-B), and Failover before mid/delay-0
edge (T5-B). pfn_chain Phase3 comments: 5s is historical Arch-A; P99 is safety
net not 500ms first-alt proof. No production logic changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant