Skip to content

fix(backend): prevent panic when txn_idx exceeds fixed buffer size - #115

Open
ayushsingh82 wants to merge 2 commits into
monad-developers:mainfrom
ayushsingh82:fix/txn-hash-tracker-panic
Open

fix(backend): prevent panic when txn_idx exceeds fixed buffer size#115
ayushsingh82 wants to merge 2 commits into
monad-developers:mainfrom
ayushsingh82:fix/txn-hash-tracker-panic

Conversation

@ayushsingh82

@ayushsingh82 ayushsingh82 commented Aug 6, 2026

Copy link
Copy Markdown

Summary

current_txn_hashes in run_event_forwarder_task tracked per-transaction hashes in a Vec<Option<[u8; 32]>> fixed at 10,000 entries, indexed directly by txn_idx. txn_idx comes straight off the event ring with no upper bound. Two of the three call sites wrote via [] indexing with no bounds check (a third, read-only, site already used .get() defensively) — so any block with txn_idx >= 10_000 panics and kills the event-forwarder task, cutting off all live data to connected clients until the health check's exit threshold restarts the process.

Changes

  • Added TxnHashTracker (backend/src/lib/txn_hash_tracker.rs), backed by a HashMap<usize, [u8; 32]> instead of a fixed-size Vec, so there's no capacity to exceed.
  • Replaced the three inline call sites in run_event_forwarder_task (server.rs) with record/get/clear on the tracker.
  • As a side effect, entries are now removed on TxnEnd instead of sitting as None forever in the old buffer.
  • Added unit tests for the new struct, including one exercising txn_idx = 50_000 to cover the exact scenario that used to panic.

Behavior for txn_idx < 10_000 is unchanged.

Test plan

  • cargo fmt --check passes on the crate
  • New unit tests pass (verified in isolation via rustc --test, since the full crate's monad-event-ring dependency requires a native toolchain — cmake + Linux hugetlbfs headers per the CI container — not available on my local macOS setup)
  • CI (cargo clippy, cargo build, cargo test in the Linux CI container) — deferred to this PR's CI run, since I could not run the full workspace build locally

Greptile Summary

The PR replaces the fixed-size transaction-hash buffer with a dynamically keyed tracker, preventing out-of-bounds panics for large transaction indices.

  • Records and retrieves transaction hashes through TxnHashTracker.
  • Removes entries at TxnEnd.
  • Resets remaining entries at BlockStart, addressing the previously reported lifetime retention of orphaned entries.
  • Adds unit coverage for large indices, clearing, overwriting, and block-level reset behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the block-start reset bounds orphaned tracker entries and resolves the previous retention issue.

Important Files Changed

Filename Overview
backend/src/lib/txn_hash_tracker.rs Introduces a HashMap-backed transaction-hash tracker with per-transaction cleanup, block reset, and focused unit tests.
backend/src/lib/server.rs Integrates the tracker into event forwarding and clears orphaned entries at each BlockStart.
backend/src/lib.rs Exposes the new transaction-hash tracker module.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[BlockStart] -->|reset tracker| B[TxnHeaderStart]
    B -->|record txn_idx and hash| C[Transaction events]
    C -->|get hash by txn_idx| D[TxnEnd]
    D -->|clear txn_idx| B
    D --> E[BlockEnd]
    E --> A
Loading

Reviews (2): Last reviewed commit: "fix(backend): reset TxnHashTracker on Bl..." | Re-trigger Greptile

Motivation:

current_txn_hashes tracked per-transaction hashes in a Vec fixed at
10_000 entries, indexed directly by txn_idx (unbounded, taken straight
off the event ring). Two of the three call sites wrote via `[]`
indexing with no bounds check, so any block with txn_idx >= 10_000
panics and kills the event-forwarder task, silently cutting off all
live data until the health check's exit threshold restarts the
process.

Modifications:

Extract the tracking into a new TxnHashTracker (backend/src/lib/txn_hash_tracker.rs),
backed by a HashMap<usize, [u8; 32]> instead of a fixed-size Vec, so
there is no capacity to exceed. Replace the three inline call sites in
run_event_forwarder_task with record/get/clear on the tracker. Added
unit tests, including one that exercises a txn_idx of 50_000 to cover
the exact scenario that used to panic.

Result:

Transaction hash tracking no longer has an upper bound on txn_idx.
Existing behavior for txn_idx < 10_000 is unchanged.
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

@ayushsingh82 is attempting to deploy a commit to the MF Flagship Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread backend/src/lib/txn_hash_tracker.rs
Motivation:

Per review feedback on this PR (Greptile): switching from a fixed-size
Vec to a HashMap removes the panic on txn_idx >= 10_000, but on its
own it trades that crash for unbounded growth in the other direction.
If a transaction's TxnEnd is ever missed (e.g. the event-ring reader
hits a Gap and calls reset(), per event_listener.rs), record() has no
matching clear() and that entry is retained for the life of the
forwarder task.

Modifications:

Add TxnHashTracker::reset(), and call it on every BlockStart in
run_event_forwarder_task. txn_idx is scoped to a single block, so
nothing left over from a previous block is ever valid to keep -
resetting on BlockStart bounds memory regardless of what gets missed
mid-block. Added a test covering a txn_idx whose TxnEnd is never
cleared, verifying reset() drops it.

Result:

Memory used by transaction hash tracking is now bounded by the
transaction count of a single block, independent of any missed
TxnEnd events.
@ayushsingh82

Copy link
Copy Markdown
Author

Addressed in 6cbf1b3: added TxnHashTracker::reset(), called on every BlockStart. Since txn_idx is scoped to a single block, this bounds memory regardless of a missed TxnEnd (e.g. from an event-ring gap) — no orphaned entries can survive past the block they were recorded in.

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