Skip to content

fix: light event#2352

Merged
ananas-block merged 7 commits intomainfrom
jorrit/fix-light-event
Mar 19, 2026
Merged

fix: light event#2352
ananas-block merged 7 commits intomainfrom
jorrit/fix-light-event

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Mar 18, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an index-out-of-bounds panic in nullifier queue processing when handling mixed batch and legacy input accounts.
  • Tests

    • Added regression tests to prevent recurrence of the panic issue.
  • Chores

    • Patch version bump to 0.23.1.

…dices

When a transaction mixes batch (v2) and legacy/concurrent (v1) input
accounts, the nullifier queue index assignment was using the raw position
in input_compressed_accounts as the write index into nullifier_queue_indices.
This caused an out-of-bounds panic when a non-batch account appeared between
batch accounts (e.g. [batchA, legacy, batchB, batchA]).

Fix by walking input_compressed_accounts in order and using a compact
batch_idx counter that only advances for accounts with a matching sequence
number entry. Non-batch accounts have no sequence number entry and are
skipped without consuming a slot.
Fetches a configurable number of blocks starting at a given slot, parses
every transaction using event_from_light_transaction, and prints a
structured summary of all Light Protocol events found.

Usage:
  cargo xtask fetch-block-events --start-slot <slot> --network mainnet
  cargo xtask fetch-block-events --start-slot <slot> --network devnet --num-blocks 5
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

This change bumps the sdk-libs/event crate version to 0.23.1, adds bs58 as a dev-dependency, and introduces regression tests targeting an index-out-of-bounds issue in nullifier queue index processing. The core logic in parse.rs is refactored from nested iteration to a single-pass approach with sequence number lookup to handle mixed batch and legacy accounts correctly.

Changes

Cohort / File(s) Summary
Manifest & Dependencies
sdk-libs/event/Cargo.toml
Version bumped to 0.23.1; bs58 added as dev-dependency
Module Declarations
sdk-libs/event/src/lib.rs
Test-only module regression_test declared with #[cfg(test)]
Core Logic
sdk-libs/event/src/parse.rs
Nullifier queue index population refactored from nested iteration to single-pass lookup with advancing batch index; non-matching legacy accounts now skipped cleanly
Regression Tests
sdk-libs/event/src/regression_test.rs, sdk-libs/event/tests/parse_test.rs
New test module targeting OOB panic on mainnet tx 407265372; new test test_mixed_batch_legacy_nullifier_queue_indices_no_oob validates correct handling of mixed batch/legacy accounts
Build Configuration
sdk-libs/justfile
Added cargo test invocation for light-event crate to test suite

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The changes span multiple file types and require careful attention to the control flow refactor in parse.rs, verification that the new logic correctly handles mixed batch/legacy account scenarios, and validation that the substantial regression test fixtures accurately represent the mainnet transaction being addressed. While most changes are additive (tests), the core logic modification demands scrutiny.

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

🔍 Index bounds were dancing wild,
Batches and legacy reconciled.
Loop becomes lookup, elegant and sound,
Mainnet tx 407265372 now safely found.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: light event' is vague and does not clearly convey the specific nature of the fix; it fails to distinguish this change from other potential fixes in the codebase. Revise the title to be more specific, such as 'fix: prevent index-out-of-bounds panic in nullifier queue processing' to clearly indicate the core issue being addressed.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jorrit/fix-light-event
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…ier OOB panic

Transaction 3ybts1eFSC7QN6aU4ao6NJCgn7xTbtBVyzeLDZJf9eVN93vHZWupX4TXqHHgV18xf17eit7Uw5T135uabnpToKK4
at slot 407265372 panicked with "index out of bounds: len is 3 but index is 3"
in create_nullifier_queue_indices when inputs mix batch and legacy trees.

Adds two tests:
- src/regression_test.rs: real mainnet instruction bytes decoded via bs58
- tests/parse_test.rs: synthetic test verifying exact nullifier_queue_indices [6, 3, 7]

Also adds light-event to sdk-libs/justfile so it runs in CI.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk-libs/event/src/parse.rs (1)

726-754: ⚠️ Potential issue | 🔴 Critical

Skip read_only inputs before consuming batch sequence numbers.

batch_input_accounts.len() is derived from insert_into_queues_instruction.nullifiers, but this helper still walks every input_compressed_account. If a transaction mixes a read-only batch input with a writable input from the same batched tree, the read-only entry will still match input_sequence_numbers, advance batch_idx, and can reproduce the same out-of-bounds write or misassign later queue indices. This is reachable today through the InvokeCpiWithReadOnly path.

🐛 Proposed fix
-    let input_merkle_tree_pubkeys = associated_instructions
-        .executing_system_instruction
-        .input_compressed_accounts
-        .iter()
-        .map(|x| {
-            associated_instructions
-                .executing_system_instruction
-                .accounts[x.merkle_context.merkle_tree_pubkey_index as usize]
-        })
-        .collect::<Vec<_>>();
+    let input_merkle_tree_pubkeys = associated_instructions
+        .executing_system_instruction
+        .input_compressed_accounts
+        .iter()
+        .filter(|input| !input.read_only)
+        .map(|input| {
+            associated_instructions
+                .executing_system_instruction
+                .accounts[input.merkle_context.merkle_tree_pubkey_index as usize]
+        })
+        .collect::<Vec<_>>();
@@
     let mut batch_idx = 0usize;
     for merkle_tree_pubkey in input_merkle_tree_pubkeys.iter() {
         if let Some(seq) = internal_input_sequence_numbers
             .iter_mut()
             .find(|s| s.tree_pubkey == *merkle_tree_pubkey)
         {
+            debug_assert!(batch_idx < nullifier_queue_indices.len());
             nullifier_queue_indices[batch_idx] = seq.seq.into();
             seq.seq += 1;
             batch_idx += 1;
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk-libs/event/src/parse.rs` around lines 726 - 754, The loop that assigns
sequence numbers walks
associated_instructions.executing_system_instruction.input_compressed_accounts
but doesn't skip read-only entries, so read-only batch inputs can consume
entries from
associated_instructions.insert_into_queues_instruction.input_sequence_numbers
and corrupt nullifier_queue_indices; update the loop to ignore any
input_compressed_account where the account is read_only (or otherwise not a
writable batch input) before trying to match and consume a sequence number, and
only increment batch_idx and write into nullifier_queue_indices when a
non-read-only (writable/batched) account matched and consumed a sequence (refer
to input_compressed_accounts, input_sequence_numbers, nullifier_queue_indices,
and batch_idx to locate and change the logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@sdk-libs/event/src/regression_test.rs`:
- Around line 103-108: The test currently only asserts result.is_ok() for the
call to event_from_light_transaction(&program_ids, &instructions, accounts)
which still allows Ok(None); change the assertion to require a parsed event by
asserting the result equals Ok(Some(_)) (or use assert!(matches!(result,
Ok(Some(_))))) so the fixture actually exercises the path that triggers
create_nullifier_queue_indices and fails if no events were parsed.

---

Outside diff comments:
In `@sdk-libs/event/src/parse.rs`:
- Around line 726-754: The loop that assigns sequence numbers walks
associated_instructions.executing_system_instruction.input_compressed_accounts
but doesn't skip read-only entries, so read-only batch inputs can consume
entries from
associated_instructions.insert_into_queues_instruction.input_sequence_numbers
and corrupt nullifier_queue_indices; update the loop to ignore any
input_compressed_account where the account is read_only (or otherwise not a
writable batch input) before trying to match and consume a sequence number, and
only increment batch_idx and write into nullifier_queue_indices when a
non-read-only (writable/batched) account matched and consumed a sequence (refer
to input_compressed_accounts, input_sequence_numbers, nullifier_queue_indices,
and batch_idx to locate and change the logic).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7346b81a-ec4d-4566-855b-ec6ed02dad68

📥 Commits

Reviewing files that changed from the base of the PR and between 2644529 and bfa7c87.

⛔ Files ignored due to path filters (5)
  • Cargo.lock is excluded by !**/*.lock and included by none
  • Cargo.toml is excluded by none and included by none
  • xtask/Cargo.toml is excluded by none and included by none
  • xtask/src/fetch_block_events.rs is excluded by none and included by none
  • xtask/src/main.rs is excluded by none and included by none
📒 Files selected for processing (6)
  • sdk-libs/event/Cargo.toml
  • sdk-libs/event/src/lib.rs
  • sdk-libs/event/src/parse.rs
  • sdk-libs/event/src/regression_test.rs
  • sdk-libs/event/tests/parse_test.rs
  • sdk-libs/justfile

Comment on lines +103 to +108
let result = event_from_light_transaction(&program_ids, &instructions, accounts);
assert!(
result.is_ok(),
"event_from_light_transaction failed: {:?}",
result.err()
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert that this fixture actually produces a parsed event.

This still passes on Ok(None), so a future pattern-matching regression could skip create_nullifier_queue_indices entirely and the test would stay green. Please assert Ok(Some(events)) so this fixture keeps exercising the failing path.

✅ Suggested tightening
-        let result = event_from_light_transaction(&program_ids, &instructions, accounts);
-        assert!(
-            result.is_ok(),
-            "event_from_light_transaction failed: {:?}",
-            result.err()
-        );
+        let events = event_from_light_transaction(&program_ids, &instructions, accounts)
+            .expect("event_from_light_transaction should parse this fixture")
+            .expect("fixture should match a Light event");
+        assert!(!events.is_empty(), "fixture should produce at least one parsed event");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let result = event_from_light_transaction(&program_ids, &instructions, accounts);
assert!(
result.is_ok(),
"event_from_light_transaction failed: {:?}",
result.err()
);
let events = event_from_light_transaction(&program_ids, &instructions, accounts)
.expect("event_from_light_transaction should parse this fixture")
.expect("fixture should match a Light event");
assert!(!events.is_empty(), "fixture should produce at least one parsed event");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sdk-libs/event/src/regression_test.rs` around lines 103 - 108, The test
currently only asserts result.is_ok() for the call to
event_from_light_transaction(&program_ids, &instructions, accounts) which still
allows Ok(None); change the assertion to require a parsed event by asserting the
result equals Ok(Some(_)) (or use assert!(matches!(result, Ok(Some(_))))) so the
fixture actually exercises the path that triggers create_nullifier_queue_indices
and fails if no events were parsed.

@ananas-block ananas-block merged commit 7075dbc into main Mar 19, 2026
24 checks passed
@ananas-block ananas-block deleted the jorrit/fix-light-event branch March 19, 2026 12:05
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.

2 participants