Skip to content

fix: correct transaction indexing - #603

Merged
bmuddha merged 3 commits into
masterfrom
bmuddha/fix/ledger-txn-indexing
Oct 31, 2025
Merged

bmuddha merged 3 commits into
masterfrom
bmuddha/fix/ledger-txn-indexing

Conversation

@bmuddha

@bmuddha bmuddha commented Oct 30, 2025 •

Copy link
Copy Markdown
Collaborator

This commit introduces a correct way to index transactions within a block, which prevents entry overwrite bugs, which led to the broken ledger replay bugs and RPC method results

Summary by CodeRabbit

  • Chores
    • Switched to per-slot automatic transaction indexing and simplified intra-slot ordering for more efficient ledger writes.
    • Adjusted logging verbosity in transaction prepare/dispatch paths (reduced from error/warn to debug).
    • Added a workspace dependency to support the new indexing mechanism.
  • Tests
    • Updated test helpers and pagination tests to match the new indexing and timing behavior.

This commit introduces a correct way to index transactions
within a block, which prevents entry overwrite bugs, which
led to the broken ledger replay bugs and RPC method results
@github-actions

github-actions Bot commented Oct 30, 2025 •

Copy link
Copy Markdown
Contributor

Manual Deploy Available

You can trigger a manual deploy of this PR branch to testnet:

Deploy to Testnet 🚀

Alternative: Comment /deploy on this PR to trigger deployment directly.

⚠️ Note: Manual deploy requires authorization. Only authorized users can trigger deployments.

Comment updated automatically when the PR is synchronized.

@coderabbitai

coderabbitai Bot commented Oct 30, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Refactors intra-slot transaction indexing from processor-managed atomic counters to a ledger-level per-slot AtomicU32 HashCache; adjusts logging from warn! to debug! in two HTTP handlers; updates tests and test helpers to align with automatic per-slot indexing and modifies a utility function's test-only visibility.

Changes

Cohort / File(s) Summary
Logging level adjustments
magicblock-aperture/src/requests/http/send_transaction.rs, magicblock-aperture/src/requests/http/simulate_transaction.rs
Changed error logging from warn! to debug!; simulate_transaction now logs only the error (omits transaction string) and uses inspect_err chaining. No control-flow or return-value changes.
Transaction pagination test updates
magicblock-aperture/tests/transactions.rs
Inserted env.advance_slots(1) before signature generation; added limit: Some(20) to GetConfirmedSignaturesForAddress2Config for the "until" pagination case.
Per-slot transaction indexing—ledger core
magicblock-ledger/Cargo.toml, magicblock-ledger/src/store/api.rs
Added workspace dependency scc; introduced block_txn_indexes: HashCache<Slot, AtomicU32> on Ledger; removed explicit transaction_slot_index parameters from write_transaction/write_transaction_status; writes now derive per-slot index via block_txn_indexes.entry(slot).or_default().1.fetch_add(1, Ordering::Relaxed); updated signature-related reads/writes and get_confirmed_signatures_for_address accordingly.
Transaction indexing utilities
magicblock-ledger/src/store/utils.rs
Made short_signature test-only (#[cfg(test)]) and changed its parameter to &solana_sdk::signature::Signature (removed local Signature import).
Test helpers refactoring
magicblock-ledger/tests/common.rs, magicblock-ledger/tests/get_block.rs, magicblock-ledger/tests/test_ledger_truncator.rs
Removed explicit transaction_slot_index argument from write_dummy_transaction signature and updated all call sites to omit the index.
Processor executor cleanup
magicblock-processor/src/executor/mod.rs, magicblock-processor/src/executor/processing.rs
Removed index: Arc<AtomicUsize> field and constructor parameter from TransactionExecutor; removed atomic index increments on ledger write paths and unused Ordering import.
Processor scheduler cleanup
magicblock-processor/src/scheduler.rs
Removed index: Arc<AtomicUsize> from TransactionScheduler, eliminated index creation/passing to executors and removed AtomicUsize import; no other scheduler control-flow changes.

Sequence Diagram(s)

sequenceDiagram
    participant Scheduler as Scheduler (old)
    participant Executor as Executor (old)
    participant LedgerOld as Ledger (old)
    participant SchedulerNew as Scheduler (new)
    participant LedgerCache as block_txn_indexes
    participant LedgerNew as Ledger (new)

    rect rgb(245, 245, 245)
    Note over Scheduler,Executor: Old flow — processor-managed intra-slot index
    Scheduler->>Executor: pass Arc<AtomicUsize> index
    Executor->>LedgerOld: write_transaction(slot, transaction_slot_index)
    LedgerOld-->>Executor: store using provided index
    end

    rect rgb(245, 255, 245)
    Note over SchedulerNew,LedgerCache: New flow — ledger-managed per-slot index
    SchedulerNew->>Executor: (no index passed)
    Executor->>LedgerCache: entry(slot).or_default().1.fetch_add(1)
    LedgerCache-->>Executor: per_slot_index
    Executor->>LedgerNew: write_transaction(slot, per_slot_index)
    LedgerNew-->>Executor: store using per-slot index
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing extra attention:
    • get_confirmed_signatures_for_address refactor: verify pagination, upper/lower bounds and ordering across intra-slot boundaries.
    • Initialization and concurrency behavior of block_txn_indexes across all ledger write paths.
    • All test helper call-site updates to ensure no leftover usages expecting an explicit transaction index.

Possibly related PRs

Suggested reviewers

  • GabrielePicco
  • thlorenz

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: correct transaction indexing" directly and clearly describes the primary objective of the changeset. The raw summary confirms that this PR fundamentally restructures how transaction indexing is performed across multiple components: it replaces an external transaction_slot_index parameter with an internal per-slot indexing mechanism in the ledger (magicblock-ledger/src/store/api.rs), removes atomic index management from the transaction executor (magicblock-processor), and aligns all supporting code (tests, helpers) with the new approach. The PR description explicitly states the goal is to "introduce a correct way to index transactions within a block to prevent entry overwrite bugs," which the title accurately captures. The title is concise, specific, and uses a conventional commit format without being vague or off-topic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bmuddha/fix/ledger-txn-indexing

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d14e9b and 8f2ed8d.

📒 Files selected for processing (1)
  • magicblock-ledger/src/store/api.rs (22 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-ledger/src/store/api.rs
📚 Learning: 2025-10-21T14:00:54.642Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#578
File: magicblock-aperture/src/requests/websocket/account_subscribe.rs:18-27
Timestamp: 2025-10-21T14:00:54.642Z
Learning: In magicblock-aperture account_subscribe handler (src/requests/websocket/account_subscribe.rs), the RpcAccountInfoConfig fields data_slice, commitment, and min_context_slot are currently ignored—only encoding is applied. This is tracked as technical debt in issue #579: https://github.com/magicblock-labs/magicblock-validator/issues/579

Applied to files:

  • magicblock-ledger/src/store/api.rs
📚 Learning: 2025-10-21T11:00:18.396Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#578
File: magicblock-aperture/src/encoder.rs:176-187
Timestamp: 2025-10-21T11:00:18.396Z
Learning: In the magicblock validator, the current slot is always the root slot. The SlotEncoder in magicblock-aperture/src/encoder.rs correctly sets `root: slot` because there is no lag between current and root slots in this architecture.

Applied to files:

  • magicblock-ledger/src/store/api.rs
🧬 Code graph analysis (1)
magicblock-ledger/src/store/api.rs (2)
magicblock-ledger/src/database/columns.rs (17)
  • slot (131-131)
  • slot (178-180)
  • slot (246-248)
  • slot (326-328)
  • slot (401-403)
  • slot (500-502)
  • slot (574-576)
  • slot (646-648)
  • index (126-126)
  • index (174-176)
  • index (211-222)
  • index (242-244)
  • index (322-324)
  • index (397-399)
  • index (496-498)
  • index (570-572)
  • index (642-644)
magicblock-ledger/src/store/utils.rs (1)
  • short_signature (62-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run_make_ci_test
  • GitHub Check: run_make_ci_test
  • GitHub Check: run_make_ci_lint
🔇 Additional comments (9)
magicblock-ledger/src/store/api.rs (9)

6-6: LGTM: Imports support the new per-slot indexing.

The addition of AtomicU32 and HashCache properly supports the thread-safe per-slot transaction indexing mechanism.

Also applies to: 15-15


74-74: LGTM: Per-slot indexing prevents transaction overwrites.

This field introduces per-slot transaction indexing to prevent entry overwrite bugs. The HashCache<Slot, AtomicU32> allows thread-safe per-slot counters that are incremented for each transaction, addressing the previous issue where transaction indexes were always 0.

Based on learnings.


172-172: LGTM: Proper initialization of the indexing cache.

The HashCache::default() correctly initializes the per-slot transaction index cache on ledger open.


507-522: LGTM: Simplified lower limit logic.

The refactoring removes oldest_slot from the tuple, simplifying the code while maintaining correct behavior. The include_slot check ensures proper boundary handling.


550-594: Excellent fix: Properly handles upper bound intra-slot filtering.

The new logic correctly addresses the previous critical issue with unconditional skips. By first finding the transaction index (lines 550-561), then explicitly checking if signature == upper_signature before skipping (lines 574-576), the code no longer incorrectly discards valid signatures when the upper_signature belongs to a different address.


604-659: LGTM: Middle range iteration logic is correct.

The code properly iterates through slots between the upper and lower boundaries, starting from the newest and working backwards. The boundary checks at lines 604 and 632 correctly handle the slot range.


685-718: Excellent fix: Properly handles lower bound intra-slot filtering.

The refactored logic correctly addresses the previous critical issue. By finding the transaction index first (lines 673-684) and explicitly filtering tx_idx > index (line 712), the code correctly includes only transactions newer than lower_signature without relying on unconditional skips that could discard valid results.

Starting the iterator with Signature::default() at line 693 is correct because it iterates in reverse from the highest indexes, and the tx_idx > index filter ensures only appropriate signatures are included.


1784-1918: LGTM: Tests properly initialize per-slot counters.

The test updates consistently call write_block before write_transaction_status, ensuring the per-slot transaction index counters are initialized. This demonstrates the correct usage pattern required by the new indexing mechanism.


994-999: Review comment is based on incorrect assumptions about call ordering.

The original comment assumes write_block must be called before write_transaction_status, but this is not the case. write_block is never called in production code—it only appears in test utilities. The production API write_transaction() directly calls write_transaction_status() without any dependency on write_block. The or_default() at lines 994-999 is the intended initialization mechanism for per-slot transaction counters, not a fallback for missing calls. The implementation is correct.

Likely an incorrect or invalid review comment.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc5b237 and 9d14e9b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • magicblock-aperture/src/requests/http/send_transaction.rs (1 hunks)
  • magicblock-aperture/src/requests/http/simulate_transaction.rs (1 hunks)
  • magicblock-aperture/tests/transactions.rs (2 hunks)
  • magicblock-ledger/Cargo.toml (1 hunks)
  • magicblock-ledger/src/store/api.rs (12 hunks)
  • magicblock-ledger/src/store/utils.rs (1 hunks)
  • magicblock-ledger/tests/common.rs (1 hunks)
  • magicblock-ledger/tests/get_block.rs (1 hunks)
  • magicblock-ledger/tests/test_ledger_truncator.rs (4 hunks)
  • magicblock-processor/src/executor/mod.rs (1 hunks)
  • magicblock-processor/src/executor/processing.rs (0 hunks)
  • magicblock-processor/src/scheduler.rs (1 hunks)
💤 Files with no reviewable changes (1)
  • magicblock-processor/src/executor/processing.rs
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-ledger/tests/common.rs
  • magicblock-processor/src/scheduler.rs
  • magicblock-processor/src/executor/mod.rs
  • magicblock-ledger/src/store/api.rs
  • magicblock-ledger/tests/get_block.rs
  • magicblock-ledger/tests/test_ledger_truncator.rs
📚 Learning: 2025-10-21T14:00:54.642Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#578
File: magicblock-aperture/src/requests/websocket/account_subscribe.rs:18-27
Timestamp: 2025-10-21T14:00:54.642Z
Learning: In magicblock-aperture account_subscribe handler (src/requests/websocket/account_subscribe.rs), the RpcAccountInfoConfig fields data_slice, commitment, and min_context_slot are currently ignored—only encoding is applied. This is tracked as technical debt in issue #579: https://github.com/magicblock-labs/magicblock-validator/issues/579

Applied to files:

  • magicblock-ledger/tests/common.rs
  • magicblock-ledger/Cargo.toml
  • magicblock-ledger/src/store/api.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler.rs
  • magicblock-processor/src/executor/mod.rs
📚 Learning: 2025-10-21T11:00:18.396Z
Learnt from: bmuddha
PR: magicblock-labs/magicblock-validator#578
File: magicblock-aperture/src/encoder.rs:176-187
Timestamp: 2025-10-21T11:00:18.396Z
Learning: In the magicblock validator, the current slot is always the root slot. The SlotEncoder in magicblock-aperture/src/encoder.rs correctly sets `root: slot` because there is no lag between current and root slots in this architecture.

Applied to files:

  • magicblock-processor/src/scheduler.rs
  • magicblock-ledger/src/store/api.rs
  • magicblock-ledger/tests/test_ledger_truncator.rs
🧬 Code graph analysis (2)
magicblock-ledger/tests/get_block.rs (1)
magicblock-ledger/tests/common.rs (1)
  • write_dummy_transaction (25-47)
magicblock-ledger/tests/test_ledger_truncator.rs (1)
magicblock-ledger/tests/common.rs (1)
  • write_dummy_transaction (25-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run_make_ci_lint
  • GitHub Check: run_make_ci_test
  • GitHub Check: run_make_ci_test
🔇 Additional comments (15)
magicblock-aperture/src/requests/http/send_transaction.rs (1)

26-28: Verify that debug level is appropriate for transaction preparation failures.

The logging level has been downgraded from warn! to debug! for transaction preparation failures. While this reduces log noise, it may also hide important errors such as malformed transactions, encoding issues, or validation failures that could indicate client integration problems or attacks.

Ensure that debug-level logging provides adequate observability for production monitoring and troubleshooting.

magicblock-aperture/tests/transactions.rs (2)

400-400: Clarify the purpose of advancing slots before generating test signatures.

The addition of env.advance_slots(1) before generating the test signatures appears related to the transaction indexing fix mentioned in the PR objectives. This likely ensures transactions are distributed across different slots for proper pagination testing.

Please confirm whether this change is necessary to test the new per-slot transaction indexing behavior, and consider adding a comment explaining why the slot advance is required for this test.


424-424: Justify the pagination limit of 20.

The "until" pagination test now includes limit: Some(20), but the test expects exactly 3 results. This limit seems arbitrarily high for the test scenario.

Please clarify:

  • Why was a limit of 20 chosen when only 3 results are expected?
  • Is this defensive against future test data expansion, or related to the indexing fix?
magicblock-aperture/src/requests/http/simulate_transaction.rs (1)

36-45: Clean error handling pattern with the same logging level consideration.

The refactoring to use inspect_err for side-effect logging while propagating errors is idiomatic and improves code clarity. The error message appropriately includes context ("to simulate").

However, the same consideration applies here as in send_transaction.rs: downgrading transaction preparation failures to debug! level may reduce visibility of important errors in production environments. Ensure this aligns with your observability requirements.

magicblock-ledger/src/store/utils.rs (1)

61-69: LGTM! Test-only helper properly scoped.

The function is correctly restricted to test builds with #[cfg(test)] and uses the fully-qualified type path.

magicblock-ledger/Cargo.toml (1)

23-23: LGTM! Dependency correctly added.

The scc dependency is properly configured with workspace versioning and is used for the HashCache concurrent data structure in the ledger's per-slot transaction indexing.

magicblock-ledger/tests/common.rs (1)

25-47: LGTM! Test helper correctly updated.

The function signature has been properly updated to remove the explicit transaction_slot_index parameter, aligning with the internal per-slot index management now handled by block_txn_indexes in the ledger.

magicblock-ledger/tests/test_ledger_truncator.rs (1)

54-54: LGTM! Test calls correctly updated.

All invocations of write_dummy_transaction have been properly updated to match the new signature without the explicit transaction index parameter.

Also applies to: 83-83, 124-124, 180-180

magicblock-ledger/tests/get_block.rs (1)

42-43: LGTM! Test correctly validates transaction ordering.

The test helper calls are properly updated, and the test continues to verify that transactions are retrieved in the correct order (lines 62-63, 67-68), which is crucial for validating the new per-slot indexing mechanism.

Also applies to: 51-52

magicblock-processor/src/executor/mod.rs (1)

1-1: LGTM! Per-executor indexing correctly removed.

The removal of the index field and associated AtomicUsize import aligns with the architectural shift to ledger-level per-slot transaction indexing. The executor no longer needs to track transaction indexes locally.

Also applies to: 31-106

magicblock-processor/src/scheduler.rs (1)

1-1: LGTM! Per-scheduler indexing correctly removed.

The removal of the index field and associated logic aligns with the shift to ledger-level transaction indexing. The scheduler no longer needs to manage or reset per-slot transaction indexes, and executor creation is properly updated.

Also applies to: 26-78, 143-147

magicblock-ledger/src/store/api.rs (4)

74-74: LGTM! Concurrent per-slot index storage properly initialized.

The HashCache<Slot, AtomicU32> field provides lock-free concurrent access for per-slot transaction indexing, and is correctly initialized with the default constructor.

Also applies to: 172-172


333-334: LGTM! Per-slot index correctly seeded.

Each new slot is seeded with an atomic counter starting at 0. The ignored return value from put() means if the slot already exists, the original counter is preserved, which prevents index resets and potential collisions.


1004-1009: LGTM! Per-slot transaction index correctly derived.

The use of entry(slot).or_default() with fetch_add(1, Ordering::Relaxed) correctly generates sequential per-slot transaction indexes in a thread-safe manner. The Relaxed ordering is appropriate since only atomicity of the increment is required, not synchronization with other operations.


610-665: LGTM! Middle range iteration logic looks correct.

The iteration between upper and lower bounds correctly:

  • Starts from the newest slot in reverse
  • Stops at the limit, non-matching address, or oldest slot
  • Skips entries where tx_slot > newest_slot (lines 643-653), which might occur due to iterator positioning edge cases

The filtering logic properly collects signatures in the intended range.

Comment thread magicblock-ledger/src/store/api.rs
Comment thread magicblock-ledger/src/store/api.rs

@thlorenz thlorenz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, please address coderabbit comments though.
And I apologize, this is probably the darkest part of the ledger .. that code is heinous.
It was spaghetti in solana itself and I didn't do a good job of improving it much.

Sorry you hade to wade in that pit.

Comment thread magicblock-aperture/src/requests/http/send_transaction.rs
Comment thread magicblock-aperture/src/requests/http/simulate_transaction.rs
Comment thread magicblock-ledger/src/store/api.rs
@bmuddha
bmuddha merged commit 52580c7 into master Oct 31, 2025
7 checks passed
@bmuddha
bmuddha deleted the bmuddha/fix/ledger-txn-indexing branch October 31, 2025 08:31
Dodecahedr0x pushed a commit that referenced this pull request Nov 18, 2025
This commit introduces a correct way to index transactions within a
block, which prevents entry overwrite bugs, which led to the broken
ledger replay bugs and RPC method results

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Switched to per-slot automatic transaction indexing and simplified
intra-slot ordering for more efficient ledger writes.
* Adjusted logging verbosity in transaction prepare/dispatch paths
(reduced from error/warn to debug).
  * Added a workspace dependency to support the new indexing mechanism.
* **Tests**
* Updated test helpers and pagination tests to match the new indexing
and timing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

3 participants