Skip to content

feat(rpc): implement morph_getTransactionHashesByReference#106

Merged
panos-xyz merged 17 commits intomainfrom
feature/morph-reference-rpc
May 9, 2026
Merged

feat(rpc): implement morph_getTransactionHashesByReference#106
panos-xyz merged 17 commits intomainfrom
feature/morph-reference-rpc

Conversation

@panos-xyz
Copy link
Copy Markdown
Contributor

@panos-xyz panos-xyz commented Apr 30, 2026

Summary

  • Add morph-reference-index crate: persistent MDBX index (4 tables) for querying MorphTx transactions by their 32-byte reference key
  • Implement morph_getTransactionHashesByReference RPC with geth-compatible response shape and hex-quantity pagination
  • Wire startup backfill + reconcile (Task A) and live ExEx incremental updates (Task B) with correct coordination

Design

See docs/superpowers/specs/2026-04-29-morph-rpc-extensions-design.md for the full spec (7 rounds of adversarial review + multiple GPT reviews before implementation).

Storage: independent MDBX instance at <datadir>/morph/reference_index/, four tables:

  • ReferenceIndex — primary query index: (reference, blockNumber, txIndex, txHash) → blockTimestamp
  • BlockReferenceIndex — reverse index for atomic block deletion: (blockNumber, txIndex, txHash) → reference
  • IndexedBlocks — canonical hash per indexed block, for offline reorg detection
  • IndexMeta — chain identity, backfill state, jade_first_block_number, snapshot metadata

Startup (Task A): paired-snapshot validation → jade sentinel re-resolution → batched backfill from jade_first_block_number (256 blocks/batch, 10ms sleep, atomic last-batch) → startup reconcile (canonical hash check + suffix gap fill) → is_ready = true → initial FinishedHeight

Live (Task B / ExEx): registered via install_exex, drains from node launch to avoid backpressure, gates writes on is_ready, gap-fills on first ready notification, handles ChainCommitted/ChainReverted/ChainReorged atomically

RPC: -32000 "reference index initializing" while not ready; -32000 "reference index is behind" when canonical_tip - indexed_to > lag_threshold (default 16); accepts geth hex-quantity offset/limit; #[method(blocking)] for MDBX reads

Correctness fixes addressed during adversarial review (3 Codex rounds)

  • TransactionVariant::WithHash (not NoHash) for all historical block reads — reth docs say NoHash produces invalid tx hashes; index keys embed tx_hash
  • reconcile scans only from indexed_from as lower bound, and skips IndexedBlocks = None (never-indexed blocks are not a mismatch)
  • InProgress + SENTINEL crash recovery re-resolves Jade against current head before marking complete
  • First-ready ChainReverted with parent > indexed_to: fills the surviving canonical gap before the revert
  • fill_gap_idempotent (delete-then-write) replaces plain append for all gap-fill paths
  • send_finished_height_monotonic keeps FinishedHeight non-regressing across startup watch vs live notification race
  • Task A startup failure propagates as panic! into spawn_critical → node shutdown (not silent error!)
  • Paired snapshot snapshot_block_hash validated against main DB provider after node start
  • alloy_serde::quantity::opt for offset/limit to accept geth hex wire format
  • Internal DB/provider errors mapped to -32603 without leaking storage details to RPC clients

Test plan

  • cargo nextest run -p morph-reference-index — 17 unit tests (key encoding, DB open/chain identity, writer idempotency, reader lag/initializing guards)
  • cargo nextest run -p morph-node --features test-utils -E 'test(reference_index)' — 3 integration tests: single tx lookup, pagination across blocks, empty result for unrelated reference
  • cargo clippy -p morph-reference-index -p morph-rpc -p morph-node --all-targets -- -D warnings
  • cargo fmt --all -- --check
  • Manual node startup smoke test (reference index opens, backfill runs, RPC responds after ready)

Summary by CodeRabbit

  • New Features
    • Adds a persistent transaction reference index with startup backfill, reconcile, and live update handling; node startup now opens and coordinates the index for readiness signaling.
  • API
    • New morph JSON-RPC namespace: getTransactionHashesByReference with pagination and protections for initialization/lag.
  • Tests
    • New unit and integration tests covering backfill, reconcile, readiness, pagination, and RPC behavior.

…age layer

- 4-table schema: ReferenceIndex, BlockReferenceIndex, IndexedBlocks, IndexMeta
- chain identity validation on DB open (chain_id, genesis_hash, schema_version)
- BackfillState (NotStarted/InProgress/Complete) + is_ready AtomicBool
- writer: write_block / delete_block (three-table atomic)
- reader: prefix cursor query with is_ready + lag threshold guards
- backfill: jade_first_block binary search + batched backfill with crash recovery
- reconcile: canonical hash check (offline reorg detection) + suffix gap fill
- metrics helpers for lag, progress, state, readiness
- 17 unit tests all passing
Implements the RPC namespace, ExEx, and node integration that back the new
reference index storage layer added in the previous commit.

- morph-rpc: new morph_ namespace with getTransactionHashesByReference
  - returns -32000 "reference index initializing" before Task A completes
  - returns -32000 "reference index is behind" when lag exceeds threshold
  - validates limit/offset bounds with -32602
- morph-node: ReferenceIndexControl (watch-channel-coordinated) wires the
  startup indexing task (Task A) with the ExEx (Task B)
  - Task A: maybe_reset_jade_sentinel -> backfill -> reconcile ->
    is_ready=true -> startup FinishedHeight
  - Task B: drains notifications from node launch, gates writes on is_ready,
    gap-fills from main DB on first is_ready notification, processes
    ChainCommitted/Reverted/Reorged with three-table atomic writes, sends
    FinishedHeight(BlockNumHash)
- MorphAddOns: optional reference_index control; when present spawns Task A
  via task_executor and registers the morph_ RPC handler
- bin/morph-reth: opens reference index DB under <datadir>/morph/reference_index,
  installs the ExEx, and injects the control into MorphAddOns
Three node-level integration tests verify the reference index storage layer:
- finds single morph_tx by reference after backfill
- paginates results across multiple blocks correctly
- returns empty for unrelated reference keys

Tests run backfill + reconcile directly against the node's provider
(no ExEx required), which is sufficient to validate the query path.
Finding 4 (Critical): NoHash → WithHash for all historical block reads
  - backfill.rs, reconcile.rs, exex/reference_index.rs fill_gap
  - reth docs say TransactionVariant::NoHash produces invalid tx hashes;
    write_block stores tx_hash in index keys, so NoHash caused silent data
    corruption in all historical indexing paths

Finding 3 (Moderate): reconcile check_start clamped to indexed_from
  - sentinel path (jade not yet active): backfill sets indexed_to=head but
    writes no IndexedBlocks, so reconcile's scan would see None != canonical
    and trigger spurious fork_height at check_start
  - fix: take max(indexed_from, indexed_to - depth) as check_start

Finding 1 (Critical): Task A startup failure upgraded to fatal
  - was: error! log and continue, leaving RPC stuck at "initializing" forever
  - now: panic propagated into spawn_critical → node shutdown on failure

Finding 5 (Moderate): first-ready gap fill is now delete-then-write
  - fill_gap renamed to fill_gap_idempotent, deletes stale entries first
  - also handles ChainReverted notification as first is_ready notification

Finding 8 (Nit): RPC method marked blocking for MDBX I/O
  - #[method(name = ..., blocking)] so jsonrpsee dispatches on blocking pool

Also: clarify write_block doc — not idempotent, caller must delete_block first
Finding F2 (High): InProgress resume clamped to jade_first_block_number
  - If crash happens between InProgress write and first batch commit,
    indexed_to is still 0; resume now uses max(indexed_to+1, jade_first)
    instead of plain indexed_to+1 to avoid re-indexing pre-Jade blocks

Finding F3 (High): RPC offset/limit use alloy_serde::quantity::opt
  - geth sends hex-encoded quantities ("0x0", "0x64") on the wire;
    plain u64 serde deserializer rejected them silently
  - matches spec constraint #2: geth compatibility first

Finding F5 (Medium): paired snapshot validation implemented
  - ReferenceIndexDb::validate_paired_snapshot(main_block_hash_fn)
    checks snapshot_block_hash/number against the main DB provider
    and returns descriptive errors on mismatch or ahead-of-DB cases
  - called from run_startup_indexing before backfill starts, after
    provider is available (matching spec timing requirement)
…arent

Scenario:
  - reconcile finishes with indexed_to = H
  - is_ready=false drain window: ExEx drains ChainCommitted{H+1}, {H+2}
    but writes nothing
  - is_ready=true, first_ready=true
  - first notification is ChainReverted { old: [H+2] }
  - revert_start = H+2, parent = H+1

Old code:
  first_ready branch only filled when revert_start <= indexed_to (false here),
  so nothing happened. handle_notification then ran delete_block(H+2) as a
  no-op and set indexed_to = parent = H+1 -- but H+1 had never been written.
  Result: indexed_to is ahead of what is actually indexed, future reconcile
  sees phantom hash mismatch on H+1.

Fix:
  In first_ready ChainReverted branch, compute parent = revert_start - 1.
  If parent > indexed_to, drain window committed canonical blocks
  (indexed_to+1..=parent) that will SURVIVE this revert; backfill them now
  via fill_gap_idempotent. handle_notification then rolls back to parent
  consistently.

  parent <= indexed_to case (revert overlaps already-indexed range) is
  already correct without special handling.
…inishedHeight

Finding 1 (High): reconcile treated indexed_hash=None as hash mismatch
  The sentinel path (Jade not yet activated) sets indexed_to=head but writes
  no IndexedBlocks entries.  reconcile then scanned [indexed_from, indexed_to],
  found None != canonical_hash, and falsely triggered reorg rebuild for
  pre-Jade blocks.  Fix: skip blocks where IndexedBlocks has no entry (None
  means "never indexed", not "hash mismatch").

  Note: indexed_from.max(depth_start) from an earlier round correctly narrows
  the scan range but did NOT prevent the None comparison since the range still
  included indexed_to itself in the sentinel case.

Finding 2 (Medium): startup FinishedHeight could regress past live commits
  tokio::select! is non-deterministic: if a ChainCommitted notification is
  processed before the startup watch channel fires, ExExEvent::FinishedHeight
  would be sent at H+k then regressed to H (the startup indexed_to).
  Fix: introduce send_finished_height_monotonic() that only sends when the
  new height strictly exceeds last_finished; both the startup watch arm and
  ChainCommitted/ChainReorged share this helper.  ChainReorged is the one
  permitted exception (reth ExEx docs allow height to go down on reorgs).
…nal error scrub

Finding 1 (Critical): InProgress + sentinel crash-recovery skips Jade history
  If node crashes between writing InProgress+SENTINEL (txn 1) and writing
  Complete (txn 2), the next restart had BackfillState::InProgress and
  jade_first_block_number=SENTINEL.  The previous fix for InProgress resume
  only avoided starting from block 1, but the sentinel fast-path inside the
  InProgress branch still blindly marked Complete without re-resolving Jade.

  Fix: when InProgress+SENTINEL, re-run resolve_jade_first_block against the
  current head.  If Jade is now active, persist the real jade_first_block_number
  and continue backfill from there.  Only take the immediate-complete shortcut
  when Jade is still not active on the current head.

Finding 2 (Moderate): to_rpc_error leaked internal Database/Provider/Other
  error strings verbatim to RPC callers.  The spec only documents two
  state-gating responses; anything else is an internal failure.
  Fix: map Database/Provider/Other to a fixed -32603 "internal reference
  index error" message; log the full error internally with tracing::error!.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@panos-xyz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 10 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d311dbc2-c12c-4bca-ab5a-6a2c25826e98

📥 Commits

Reviewing files that changed from the base of the PR and between a41b55b and a02d289.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • crates/node/tests/it/reference_index.rs
  • deny.toml
📝 Walkthrough

Walkthrough

Adds a new morph-reference-index crate providing a persistent reference index (DB, tables, writer/reader, backfill, reconcile, ExEx). The node opens the DB at startup, runs startup indexing, exposes a morph_ JSON-RPC method, and wires the reader into RPC and add-ons.

Changes

Reference Index Feature & Integration

Layer / File(s) Summary
Workspace / Manifest
Cargo.toml, bin/morph-reth/Cargo.toml, crates/node/Cargo.toml, crates/rpc/Cargo.toml, crates/reference-index/Cargo.toml
Adds workspace member crates/reference-index, workspace alias morph-reference-index (path), and git reth-* workspace deps; updates crates to depend on morph-reference-index.
Crate Surface
crates/reference-index/src/lib.rs
New crate entrypoint exporting modules and re-exporting ReferenceIndexDb, ReferenceIndexReader, types, schema constants, and defaults.
Data Schema & Types
crates/reference-index/src/tables.rs, crates/reference-index/src/types.rs
Defines table schemas, fixed-length key/value encodings, BackfillState, ReferenceQuery, ReferenceTransactionResult, SCHEMA_VERSION, sentinel constant, and error enum.
Storage Layer
crates/reference-index/src/db.rs
Adds ReferenceIndexDb (MDBX wrapper), chain-identity init/validation on open, readiness flag, metadata codec helpers and readers, tx factories, and unit tests.
Write Path
crates/reference-index/src/writer.rs
Implements write_block and delete_block, plus metadata writers (indexed_from/to, backfill_current, backfill_state, jade_first_block_number) and tests.
Backfill
crates/reference-index/src/backfill.rs
Implements resolve_jade_first_block, run_backfill, and maybe_reset_jade_sentinel with checkpointing, batch commits, sentinel handling, and tests.
Startup Reconcile
crates/reference-index/src/reconcile.rs
Implements run_startup_reconcile to detect/rollback reorg forks and fill suffix gaps up to head.
Reader & Query Logic
crates/reference-index/src/reader.rs
Adds ReferenceIndexReader::query enforcing readiness and lag threshold, cursor seek iteration, offset/limit pagination, and unit tests.
ExEx Integration
crates/node/src/exex/mod.rs, crates/node/src/exex/reference_index.rs
Adds reference_index ExEx: ReferenceIndexControl, reference_index_exex task loop, startup drain behavior, gap-fill and per-notification atomic handling, monotonic FinishedHeight signaling, and run_startup_indexing.
Node Add-Ons Wiring
crates/node/src/add_ons.rs, crates/node/src/lib.rs, bin/morph-reth/src/main.rs
Node pre-opens DB producing ReferenceIndexControl and startup receiver; MorphAddOns gains optional reference_index control and with_reference_index; startup indexing spawned in blocking task and RPC handler wired with ReferenceIndexReader.
RPC Surface & Handler
crates/rpc/src/lib.rs, crates/rpc/src/morph/mod.rs, crates/rpc/src/morph/rpc.rs, crates/rpc/src/morph/handler.rs
Adds morph_ JSON-RPC namespace, ReferenceQueryArgs DTO, getTransactionHashesByReference RPC method, MorphRpc/MorphRpcHandler wiring, and conversion of ReferenceIndexError → JSON-RPC errors.
Integration Tests
crates/node/tests/it/main.rs, crates/node/tests/it/reference_index.rs
Adds Tokio integration tests that start a test node, inject MorphTx transactions with references, run backfill/reconcile/startup, open ReferenceIndexDb, query via ReferenceIndexReader, and assert pagination, ordering, and empty-result behavior.

Sequence Diagram(s)

sequenceDiagram
    participant NodeBin as Node Binary
    participant RefDB as ReferenceIndexDB
    participant Builder as Node Builder / AddOns
    participant Startup as Startup Indexing Task
    participant ExEx as ExEx Task
    participant Chain as Blockchain Provider

    NodeBin->>RefDB: open(chain_id, genesis_hash)
    RefDB->>RefDB: validate_or_init_chain_identity()
    NodeBin->>Builder: with_reference_index(control)
    Builder->>Startup: spawn_blocking(run_startup_indexing)
    Startup->>Chain: read headers / resolve Jade first block
    Chain-->>Startup: headers / jade_block
    Startup->>RefDB: run_backfill(batch writes)
    RefDB->>RefDB: write indexed blocks & meta
    Startup->>RefDB: run_startup_reconcile()
    RefDB->>Chain: verify canonical hashes / detect reorg
    RefDB->>RefDB: fill gaps / update indexed_to
    Startup->>RefDB: mark_startup_finished() -> signal
    ExEx->>RefDB: await readiness signal
    ExEx->>Chain: receive block notifications
    ExEx->>RefDB: apply transactional updates per notification
Loading
sequenceDiagram
    participant Client as JSON-RPC Client
    participant Rpc as Morph RPC Handler
    participant Reader as ReferenceIndexReader
    participant RefDB as ReferenceIndexDB

    Client->>Rpc: morph_getTransactionHashesByReference(args)
    Rpc->>Reader: build ReferenceQuery + canonical_tip
    Reader->>RefDB: is_ready()?
    alt not ready
        RefDB-->>Reader: not ready
        Reader-->>Rpc: IndexNotInitialized error
        Rpc-->>Client: JSON-RPC error
    else ready
        Reader->>RefDB: indexed_to (compute lag)
        alt lag > threshold
            Reader-->>Rpc: IndexBehind error
            Rpc-->>Client: JSON-RPC error
        else
            Reader->>RefDB: open read tx, seek reference key
            Reader->>RefDB: iterate, apply offset/limit
            Reader-->>Rpc: Vec<ReferenceTransactionResult>
            Rpc-->>Client: JSON-RPC result
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • morph-l2/morph-reth#19 — related: changes to Morph transaction reference fields used by the reference-index writer/reader.
  • morph-l2/morph-reth#20 — related: RPC surface and morph namespace wiring overlaps with this PR.
  • morph-l2/morph-reth#35 — related: modifications to node add-ons and wiring that intersect with this PR.

Suggested reviewers

  • chengwenxi
  • anylots

Poem

🐰 I dug a little index beneath chain-lit logs,
Wrote hashes, filled gaps, and chased Jade frogs.
Startup hummed, ExEx danced, RPCs now find,
Transactions by reference — tidy and kind.
Hop, commit, and carrot — the index is signed!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main feature being added: implementation of the morph_getTransactionHashesByReference RPC method. It is concise, clear, and reflects the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/morph-reference-rpc

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/node/src/exex/reference_index.rs`:
- Around line 95-104: The startup branch forwards a potentially stale
FinishedHeight (startup_rx) that can be rendered non-canonical by an intervening
ChainReorged; before calling send_finished_height_monotonic(&ctx.events, block,
&mut last_finished) re-validate the startup payload against the current index
state and canonical hash (e.g. compare block.number and block.hash to
control.db.indexed_to() and the chain's canonical hash) and skip/dismiss the
startup payload if any notification (ChainReorged or other index mutations) has
already advanced or changed the canonical head; apply the same guard to the
other occurrence around send_finished_height_monotonic noted in the review
(lines referenced 359-370) to avoid re-advancing pruning to a non-canonical
block.

In `@crates/reference-index/src/db.rs`:
- Around line 169-181: The code currently allows reopening when
IndexMeta::SchemaVersion is missing (stored_schema == None), which lets a
pre-schema DB be opened as if current; update the reopen logic to reject a
missing schema version when other index metadata exists: after retrieving
stored_schema via
tx.get::<crate::tables::IndexMeta>(IndexMetaKey::SchemaVersion.into()), if
stored_schema is None then check for presence of other meta keys (e.g.
tx.get::<crate::tables::IndexMeta>(IndexMetaKey::ChainId.into()) and
tx.get(...IndexMetaKey::GenesisHash.into())); if any of those return Some(_)
then return an error (use ReferenceIndexError::SchemaMismatch or a new
MissingSchemaVersion variant) instead of falling through, otherwise allow
initialization as before. Ensure you still compare Some(v) != SCHEMA_VERSION as
in the existing match.

In `@crates/reference-index/src/reconcile.rs`:
- Around line 84-90: The rollback path in reconcile.rs currently deletes
BlockReferenceIndex and IndexedBlocks via delete_block(&tx, number) and updates
indexed_to, but it does not remove associated ReferenceIndex rows, leaving
reverted tx hashes still queryable by reader.query(); modify the rollback helper
to also delete matching ReferenceIndex entries within the same transaction
(extend delete_block or add a delete_reference_index(&tx, number) call so
ReferenceIndex rows for that block are removed before tx.commit()), and add a
reorg regression test that performs an offline reorg and asserts that
reader.query() no longer returns reverted transaction hashes.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: bdde8bdd-87f9-4290-b548-ddd0771e0c77

📥 Commits

Reviewing files that changed from the base of the PR and between faec4c8 and 4afc286.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • Cargo.toml
  • bin/morph-reth/Cargo.toml
  • bin/morph-reth/src/main.rs
  • crates/node/Cargo.toml
  • crates/node/src/add_ons.rs
  • crates/node/src/exex/mod.rs
  • crates/node/src/exex/reference_index.rs
  • crates/node/src/lib.rs
  • crates/node/tests/it/main.rs
  • crates/node/tests/it/reference_index.rs
  • crates/reference-index/Cargo.toml
  • crates/reference-index/src/backfill.rs
  • crates/reference-index/src/db.rs
  • crates/reference-index/src/lib.rs
  • crates/reference-index/src/reader.rs
  • crates/reference-index/src/reconcile.rs
  • crates/reference-index/src/tables.rs
  • crates/reference-index/src/types.rs
  • crates/reference-index/src/writer.rs
  • crates/rpc/Cargo.toml
  • crates/rpc/src/lib.rs
  • crates/rpc/src/morph/handler.rs
  • crates/rpc/src/morph/mod.rs
  • crates/rpc/src/morph/rpc.rs

Comment thread crates/node/src/exex/reference_index.rs
Comment thread crates/reference-index/src/db.rs
Comment thread crates/reference-index/src/reconcile.rs
SnapshotBlockNumber/SnapshotBlockHash metadata keys and validate_paired_snapshot()
had no write path — ops tar-compress the datadir directly so these fields were
always empty and the check was always a no-op. chain_id + genesis_hash at open()
plus reconcile's canonical hash check already cover the mismatch cases that matter.
Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
crates/node/src/exex/reference_index.rs (1)

95-104: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Re-validate the startup FinishedHeight before forwarding it.

This is still racy with shorter reorgs. If a ChainReorged is handled before this watch arm runs, send_finished_height_monotonic can re-send the stale startup (number, hash) and move pruning back onto a non-canonical block.

Suggested fix
             changed = startup_rx.changed() => {
                 if changed.is_ok() && let Some(block) = *startup_rx.borrow_and_update() {
+                    let current_number = control.db.indexed_to()?;
+                    let current_hash = ctx.components.provider().block_hash(current_number)?;
+                    if current_number != block.number || current_hash != Some(block.hash) {
+                        debug!(
+                            target: "morph::reference_index",
+                            startup_number = block.number,
+                            current_number,
+                            "dropping stale startup FinishedHeight"
+                        );
+                        continue;
+                    }
                     debug!(
                         target: "morph::reference_index",
                         block_number = block.number,
                         "startup complete; forwarding initial FinishedHeight"
                     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/node/src/exex/reference_index.rs` around lines 95 - 104, Re-validate
the startup FinishedHeight after the startup_rx.changed() branch but before
calling send_finished_height_monotonic: after reading block from
startup_rx.borrow_and_update(), check the current chain tip or recent reorg flag
(the same check used when handling ChainReorged events) to ensure the (number,
hash) is still canonical; if it is no longer canonical skip forwarding and do
not update last_finished. Locate the watch arm using startup_rx, the call to
send_finished_height_monotonic(&ctx.events, block, &mut last_finished), and the
ChainReorged handling logic to reuse the canonical-validation check so the
startup message cannot re-send a stale FinishedHeight.
crates/reference-index/src/db.rs (1)

167-178: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject reopen when schema_version is missing.

If ChainId/GenesisHash are already persisted but SchemaVersion is absent, this still falls through as success. That lets a pre-schema or partially initialized index be read with the current layout.

Suggested fix
         match stored_schema {
             Some(v) if v != SCHEMA_VERSION => {
                 return Err(ReferenceIndexError::SchemaMismatch {
                     expected: SCHEMA_VERSION,
                     actual: v,
                 });
             }
-            _ => {}
+            Some(_) => {}
+            None => {
+                return Err(ReferenceIndexError::Other(eyre::eyre!(
+                    "reference index schema_version missing"
+                )));
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/reference-index/src/db.rs` around lines 167 - 178, The code currently
lets stored_schema == None pass, allowing a partially-initialized index to be
treated as valid; update the match on stored_schema (the result of
tx.get::<crate::tables::IndexMeta>(IndexMetaKey::SchemaVersion.into())) so that
None is treated as an error rather than success: either return
Err(ReferenceIndexError::SchemaMissing) if you prefer a dedicated error variant,
or return Err(ReferenceIndexError::SchemaMismatch { expected: SCHEMA_VERSION,
actual: 0 }) for backwards compatibility; make this change in the stored_schema
match branch (and add the new ReferenceIndexError::SchemaMissing variant if you
choose that route).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/reference-index/src/writer.rs`:
- Around line 151-160: The function set_jade_first_block_number in writer.rs is
not formatted to project rustfmt standards; run rustfmt (cargo fmt) on the file
(or the repo) to reformat this function and surrounding code so the Rustfmt CI
job passes, ensuring code around IndexMetaKey::JadeFirstBlockNumber,
encode_u64(block_number), and the tx.put::<IndexMeta>(...) call is adjusted to
the project's style rules.

---

Duplicate comments:
In `@crates/node/src/exex/reference_index.rs`:
- Around line 95-104: Re-validate the startup FinishedHeight after the
startup_rx.changed() branch but before calling send_finished_height_monotonic:
after reading block from startup_rx.borrow_and_update(), check the current chain
tip or recent reorg flag (the same check used when handling ChainReorged events)
to ensure the (number, hash) is still canonical; if it is no longer canonical
skip forwarding and do not update last_finished. Locate the watch arm using
startup_rx, the call to send_finished_height_monotonic(&ctx.events, block, &mut
last_finished), and the ChainReorged handling logic to reuse the
canonical-validation check so the startup message cannot re-send a stale
FinishedHeight.

In `@crates/reference-index/src/db.rs`:
- Around line 167-178: The code currently lets stored_schema == None pass,
allowing a partially-initialized index to be treated as valid; update the match
on stored_schema (the result of
tx.get::<crate::tables::IndexMeta>(IndexMetaKey::SchemaVersion.into())) so that
None is treated as an error rather than success: either return
Err(ReferenceIndexError::SchemaMissing) if you prefer a dedicated error variant,
or return Err(ReferenceIndexError::SchemaMismatch { expected: SCHEMA_VERSION,
actual: 0 }) for backwards compatibility; make this change in the stored_schema
match branch (and add the new ReferenceIndexError::SchemaMissing variant if you
choose that route).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f60dc856-8bd6-4037-8652-db2b6fbb3c99

📥 Commits

Reviewing files that changed from the base of the PR and between 4afc286 and b6aa870.

📒 Files selected for processing (3)
  • crates/node/src/exex/reference_index.rs
  • crates/reference-index/src/db.rs
  • crates/reference-index/src/writer.rs

Comment thread crates/reference-index/src/writer.rs
panos-xyz added 2 commits May 6, 2026 17:07
- ReferenceIndexDb: drop highest_indexed_block / highest_block_reference_index
  (no callers in the workspace)
- ReferenceIndexDb: remove unused DbCursorRO import exposed by the deletion
- ReferenceIndexReader: drop db() accessor (no callers in the workspace)
…p error wrapping

- Extract const TARGET in backfill, reconcile, exex and handler so the
  tracing target string is defined once per file and easy to grep
- Remove the 10ms inter-batch sleep in run_backfill; on a 2.6M-block
  backfill (256-block batches) this was ~100s of pure idle time with no
  benefit
- Simplify best_block_number error path in MorphRpcHandler: use the
  existing From<ProviderError> impl instead of wrapping with eyre::eyre!
panos-xyz and others added 4 commits May 9, 2026 15:48
- drop redundant `db.clone()` in reference_index integration test
- remove RUSTSEC-2026-0002 from advisory ignore list (no longer matched)
Update transitive dependencies where cargo can select patched releases, and document the hickory-proto advisory exceptions that remain pinned by the current reth dependency graph.

Constraint: PR #106 cargo-deny failure blocks merge
Rejected: Broadly ignore all new advisories | patched multihash and rustls-webpki versions are available
Confidence: medium
Scope-risk: narrow
Not-tested: Skipped additional verification per user request
@panos-xyz panos-xyz merged commit 1e8b430 into main May 9, 2026
13 checks passed
@panos-xyz panos-xyz deleted the feature/morph-reference-rpc branch May 9, 2026 08:29
@panos-xyz panos-xyz mentioned this pull request May 9, 2026
3 tasks
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