Skip to content

Move reorg detection into Rust BlockStore - #1405

Open
DZakh wants to merge 15 commits into
mainfrom
claude/block-store-reorg-tracking-2ucqi4
Open

Move reorg detection into Rust BlockStore#1405
DZakh wants to merge 15 commits into
mainfrom
claude/block-store-reorg-tracking-2ucqi4

Conversation

@DZakh

@DZakh DZakh commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

Reorg detection logic has been moved from ReScript into the Rust BlockStore, where it can operate directly on stored block hashes during page merges. This eliminates the separate ReorgDetection module and integrates hash comparison into the core block storage mechanism.

Key Changes

Rust BlockStore (packages/cli/src/block_store.rs)

  • Added FuelBlockField enum and support for Fuel block storage (height, id, time fields)
  • Changed EVM hash column from fixed 32-byte to variable-width to support test hashes of arbitrary length
  • Added HashMismatch struct to report detected reorg events with block number and hash details
  • Implemented merge() method that compares block hashes between stored and incoming pages, detecting the lowest in-threshold mismatch
  • Added get_hash() and getHashedBlockNumbers() accessors for reading stored hashes and querying blocks within reorg threshold
  • Added from_js_evm(), from_js_svm(), from_js_fuel() factory methods to build pages from sparse JS block objects
  • Implemented pending_mismatch tracking to detect hash conflicts within a single response (same block observed twice with different hashes)
  • Added decode_hex_bytes() utility for strict 0x-prefixed hex validation

ReScript Runtime (packages/envio/src/)

  • Removed ReorgDetection.t type and all detection logic; kept only data shape types (blockData, reorgDetected)
  • Updated ChainState to store shouldRollbackOnReorg and maxReorgDepth directly instead of a ReorgDetection instance
  • Modified BlockStore bindings to expose merge() with hash comparison, getHash(), and getHashedBlockNumbers()
  • Updated Batch to read block hashes from BlockStore instead of ReorgDetection
  • Updated Rollback to query stored hashes via BlockStore.getHashedBlockNumbers()
  • Seeded persistent store with reorg checkpoints on resume by merging a hash-only page

Test Updates

  • Rewrote ReorgDetection_test.res to test BlockStore merge behavior instead of the removed module
  • Updated mock helpers to build pages via BlockStore.fromJs() and verify hashes via BlockStore.getHash()
  • Fixed test block hashes to use even-length hex strings (e.g., "0x0102" instead of "0x102") to pass validation
  • Updated rollback tests to work with the new hash storage model

Notable Implementation Details

  • Hash comparison happens at merge time with a configurable threshold (fromBlock parameter); blocks below the threshold are merged without comparison
  • The report_only flag enables detect-only mode: mismatches are reported but the merge proceeds anyway, preventing re-reporting on subsequent responses
  • Within-page conflicts (same block number with different hashes in one response) are tracked separately and compared against the threshold
  • The hash column is variable-width to allow test pages with short mock hashes while fetched blocks always store full 32-byte hashes
  • Reorg checkpoints are persisted as hash-only rows (no other block fields) and merged into the store on indexer resume

https://claude.ai/code/session_01Aj6SS9KbG9mytdzuYnMs3a

Summary by CodeRabbit

  • New Features
    • Added Fuel block support to BlockStore (height, ID, time) with Fuel field discovery.
    • Unified reorg/hash handling across EVM/SVM/Fuel using BlockStore; sources now expose block data via blockStore.
    • Added getBlockHashes support returning BlockStore and request timing stats; introduced ENVIO_MAX_SOURCE_RETRIES to cap retries.
  • Bug Fixes
    • Improved reorg detection accuracy with within-page conflict handling and “lowest mismatch” reporting.
    • More tolerant EVM hash decoding for variable-length inputs.
  • Breaking Changes
    • Reorg/hash workflows now use blockStore instead of a standalone blockHashes array; related source/update logic has been updated.

claude added 4 commits July 10, 2026 12:49
The per-chain BlockStore now owns reorg detection: merging a fetch-response
page compares block hashes and reports the lowest in-threshold mismatch
(discarding the page in rollback mode, overwriting in detect-only mode),
pruning keeps in-threshold hashes as hash-only rows, and rollback reads
(getHash, getHashedBlockNumbers, latestValidBlock) replace the JS-side
ReorgDetection registry.

- Fuel gets a first-class store (height/time/id) built by the Rust client;
  Fuel blocks are materialised from the store instead of carried inline.
- BlockStore.fromJs builds pages from sparse JS blocks: RPC contributes
  hash-only observations, simulate an empty page, and stored reorg
  checkpoints seed the store on resume.
- The EVM HyperSync rollback-guard blocks are inserted into the page store
  on the Rust side; sources no longer return a separate blockHashes array.
- ReorgDetection.res shrinks to the shared data types and log params.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj6SS9KbG9mytdzuYnMs3a
- Store detection hashes as their JS string form for every ecosystem, so
  pages built from JS observations compare byte-for-byte with fetched blocks
  (and mismatch reports return the original strings).
- Record within-page hash conflicts (the same block observed twice with
  different hashes in one response) while a page is built and report them
  from merge, matching the old duplicate-collision detection.
- Rollback keeps hash-only rows on non-reorg chains — their scanned hashes
  stay valid while refetch repopulates the data — and drops everything above
  the target on the reorg chain.
- Checkpoint block hashes are gated by the chain's own reorg threshold
  (sourceBlockNumber - maxReorgDepth) instead of the global flag.
- Port ReorgDetection/SourceBlockHashes/ChainState/rollback tests to the
  store-backed API and pin Fuel.blockFields against the Rust ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj6SS9KbG9mytdzuYnMs3a
- FuelBlockField orders id first (Fuel.res blockFields matches).
- The within-page hash conflict lives inside the store's single Mutex
  alongside the table instead of a second lock; it stays on the struct
  because a page is built across several insert calls (response blocks,
  then guard rows) and merge reads it later.
- Clarify why the EVM hash column is filled outside evm_block_col.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj6SS9KbG9mytdzuYnMs3a
EVM/Fuel block hashes are hex-validated and stored as bytes again: fromJs
pages reject non-hex hashes (e.g. arbitrary marker strings) with a
validation error instead of storing them opaquely. The hash column is
variable-width — 32 bytes for fetched blocks — so hex test fixtures can
stay short. Test mocks now use valid even-length hex hashes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj6SS9KbG9mytdzuYnMs3a
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change adds Fuel block storage, moves reorg detection into BlockStore merges, replaces ReorgDetection state with BlockStore-backed APIs, updates source responses and native request handling, and adds bounded retries, validation, materialization changes, and related tests.

Changes

BlockStore and source pipeline

Layer / File(s) Summary
Rust BlockStore and storage semantics
packages/cli/src/block_store.rs, packages/cli/src/field_table.rs
Adds Fuel rows and fields, byte-level conflict detection, threshold-aware merge results, hash retention, rollback controls, and Fuel field-order exports.
Source BlockStore pages and native requests
packages/cli/src/*_hypersync_source/*, packages/envio/src/sources/*
Sources build and return BlockStore pages, including Fuel decoding, RPC parent hashes, SVM pagination, EVM rollback guards, and request statistics.
ChainState reorg flow and retry validation
packages/envio/src/ChainState.*, packages/envio/src/sources/SourceManager.*, packages/envio/src/Batch.res
ChainState stores reorg configuration and delegates comparisons to BlockStore; SourceManager validates pages, retries inconsistent responses, and enforces retry limits.
Mocks, fixtures, and test configuration
scenarios/test_codegen/test/*, packages/envio/src/TestIndexer.res, packages/envio/src/bindings/Vitest.res, packages/cli/templates/*, packages/e2e-tests/*
Tests and mocks adopt BlockStore responses, cover reorg and retry behavior, normalize hashes, and adjust retry and timeout settings.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 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 clearly summarizes the main change: moving reorg detection into the Rust BlockStore.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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

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

🧹 Nitpick comments (4)
scenarios/test_codegen/test/ReorgDetection_test.res (2)

3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the refactor narration.

This describes where reorg detection moved rather than a non-obvious behavioral constraint.

As per coding guidelines, **/*.res: “Never narrate the refactor itself.”

Proposed change
-// Reorg detection now lives in the Rust BlockStore: merging a page compares
-// block hashes and reports the lowest in-threshold mismatch; pruning keeps
-// in-threshold hashes; rollback reads find the last valid block.
 describe("Block store reorg detection", () => {
🤖 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 `@scenarios/test_codegen/test/ReorgDetection_test.res` around lines 3 - 5,
Remove the refactor-narration comment at the top of the test file; leave the
test implementation unchanged.

Source: Coding guidelines


35-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use one aggregate assertion per test.

  • scenarios/test_codegen/test/ReorgDetection_test.res#L35-L59: collect threshold-query results into one record assertion.
  • scenarios/test_codegen/test/ReorgDetection_test.res#L88-L122: collect rollback and report-only outcomes into one final assertion, or split the modes into separate tests.
  • scenarios/test_codegen/test/ReorgDetection_test.res#L141-L164: compare conflicting and identical duplicate outcomes together.
  • scenarios/test_codegen/test/ReorgDetection_test.res#L191-L218: compare all latestValidBlock cases in one array or record.
  • scenarios/test_codegen/test/SourceBlockHashes_test.res#L195-L217: aggregate item count and both hash-presence checks.
  • scenarios/test_codegen/test/SourceBlockHashes_test.res#L253-L271: aggregate item count, fetched block number, and hashes.

As per coding guidelines, **/*_test.res: “Always use single assert to check the whole value instead of multiple asserts for every field.”

🤖 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 `@scenarios/test_codegen/test/ReorgDetection_test.res` around lines 35 - 59,
Replace the multiple assertions in the affected tests with one aggregate
assertion per test: in scenarios/test_codegen/test/ReorgDetection_test.res
ranges 35-59, 88-122, 141-164, and 191-218, collect the threshold,
rollback/report-only, duplicate, and latestValidBlock outcomes into a single
record or array assertion; in
scenarios/test_codegen/test/SourceBlockHashes_test.res ranges 195-217 and
253-271, aggregate the item count, fetched block number, and hash-presence
results into one assertion while preserving all expected values.

Source: Coding guidelines

packages/envio/src/Batch.res (1)

15-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the caller-oriented field comment.

The comment only explains where blockStore is consumed rather than a non-obvious constraint.

As per coding guidelines, “Don't write a comment that restates what the code already says — … which callers use a value.”

🤖 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 `@packages/envio/src/Batch.res` around lines 15 - 16, Remove the
caller-oriented comment above the chain block store field in Batch.res, leaving
the field declaration and surrounding code unchanged.

Source: Coding guidelines

packages/envio/src/sources/RpcSource.res (1)

1118-1122: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Dedupe observedBlocks entries per block number before pushing per-log.

This loop pushes one {blockNumber, blockHash} entry per log item. For a block containing many matched logs (common for popular contracts), the same (blockNumber, blockHash) pair gets pushed once per log, inflating the page passed to BlockStore.fromJs/fromJsEvm across the napi boundary for no benefit — the block's hash doesn't change between logs.

♻️ Proposed dedup
+    let seenLogBlocks = Utils.Set.make()
     items->Array.forEach(({log}) =>
-      observedBlocks
-      ->Array.push({BlockStore.blockNumber: log.blockNumber, blockHash: log.blockHash})
-      ->ignore
+      if !(seenLogBlocks->Utils.Set.has(log.blockNumber)) {
+        seenLogBlocks->Utils.Set.add(log.blockNumber)->ignore
+        observedBlocks
+        ->Array.push({BlockStore.blockNumber: log.blockNumber, blockHash: log.blockHash})
+        ->ignore
+      }
     )
🤖 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 `@packages/envio/src/sources/RpcSource.res` around lines 1118 - 1122,
Deduplicate observedBlocks by block number before adding entries in the items
iteration around the log handling flow. Ensure each block contributes only one
{BlockStore.blockNumber, blockHash} pair, while preserving the existing block
hash and the downstream BlockStore.fromJs/fromJsEvm input behavior.
🤖 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 `@packages/cli/src/block_store.rs`:
- Around line 658-665: Update the merge method containing the ecosystem
assertion to enforce compatibility in all builds: replace the debug-only check
with a runtime discriminant comparison, return the method’s existing error type
on mismatch, and perform this validation before accessing either table. Preserve
the current merge behavior for matching ecosystems.
- Around line 995-999: Update the Height and ParentSlot conversions in the
block-store field factory to propagate u64 conversion failures like the existing
slot handling. Reject negative values as errors instead of converting them to
missing fields, while preserving successful conversion behavior for valid
values.

In `@packages/envio/src/ChainState.res`:
- Around line 25-26: Use cs.maxReorgDepth as the authoritative runtime value
across reorg detection, pruning, rollback, and checkpoint retention. In
packages/envio/src/ChainState.res:25-26 and getHighestBlockBelowThreshold, use
cs.maxReorgDepth; at packages/envio/src/ChainState.res:743-744, include
maxReorgDepth: cs.maxReorgDepth in Batch.chainBeforeBatch; and in
packages/envio/src/Batch.res:208-209, calculate the retention threshold from
chainBeforeBatch.maxReorgDepth instead of chainConfig.maxReorgDepth.

In `@scenarios/test_codegen/test/rollback/Rollback_test.res`:
- Around line 2084-2089: The comments in the rollback test still reference
removed reorg helper methods. Update the affected comments near the second reorg
and the additional referenced sections to describe BlockStore’s current hash
comparison and rollback behavior, including the relevant stored-block and
threshold outcomes without naming getThresholdBlockNumbersBelowBlock or
registerReorgGuard.

---

Nitpick comments:
In `@packages/envio/src/Batch.res`:
- Around line 15-16: Remove the caller-oriented comment above the chain block
store field in Batch.res, leaving the field declaration and surrounding code
unchanged.

In `@packages/envio/src/sources/RpcSource.res`:
- Around line 1118-1122: Deduplicate observedBlocks by block number before
adding entries in the items iteration around the log handling flow. Ensure each
block contributes only one {BlockStore.blockNumber, blockHash} pair, while
preserving the existing block hash and the downstream
BlockStore.fromJs/fromJsEvm input behavior.

In `@scenarios/test_codegen/test/ReorgDetection_test.res`:
- Around line 3-5: Remove the refactor-narration comment at the top of the test
file; leave the test implementation unchanged.
- Around line 35-59: Replace the multiple assertions in the affected tests with
one aggregate assertion per test: in
scenarios/test_codegen/test/ReorgDetection_test.res ranges 35-59, 88-122,
141-164, and 191-218, collect the threshold, rollback/report-only, duplicate,
and latestValidBlock outcomes into a single record or array assertion; in
scenarios/test_codegen/test/SourceBlockHashes_test.res ranges 195-217 and
253-271, aggregate the item count, fetched block number, and hash-presence
results into one assertion while preserving all expected values.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2cc44269-3e3e-4112-aaa9-fd867381eeb0

📥 Commits

Reviewing files that changed from the base of the PR and between 12c9fa4 and 7e4d2ad.

📒 Files selected for processing (37)
  • packages/cli/src/block_store.rs
  • packages/cli/src/evm_hypersync_source/mod.rs
  • packages/cli/src/field_table.rs
  • packages/cli/src/fuel_hypersync_source/mod.rs
  • packages/envio/src/Batch.res
  • packages/envio/src/ChainFetching.res
  • packages/envio/src/ChainState.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/Core.res
  • packages/envio/src/EventConfigBuilder.res
  • packages/envio/src/EventProcessing.res
  • packages/envio/src/ReorgDetection.res
  • packages/envio/src/Rollback.res
  • packages/envio/src/SimulateItems.res
  • packages/envio/src/sources/BlockStore.res
  • packages/envio/src/sources/Fuel.res
  • packages/envio/src/sources/HyperFuel.res
  • packages/envio/src/sources/HyperFuel.resi
  • packages/envio/src/sources/HyperFuelClient.res
  • packages/envio/src/sources/HyperFuelSource.res
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/sources/SimulateSource.res
  • packages/envio/src/sources/Source.res
  • packages/envio/src/sources/SvmHyperSyncSource.res
  • scenarios/fuel_test/src/Indexer.res
  • scenarios/test_codegen/test/BlockStore_test.res
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/ReorgDetection_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • scenarios/test_codegen/test/SourceBlockHashes_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/lib_tests/ChainState_materialize_test.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res
  • scenarios/test_codegen/test/rollback/ChainMocking.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res

Comment on lines +658 to +665
// A page and its persistent store are the same per-chain ecosystem, so
// the decoder is unaffected by the merge. Only the kind matters: the
// EVM checksum flag lives on the persistent store's decoder and may
// differ on a page built via `fromJs`.
debug_assert_eq!(
std::mem::discriminant(&self.ecosystem),
std::mem::discriminant(&page.ecosystem)
);

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Enforce ecosystem compatibility in release builds.

debug_assert_eq! disappears in release builds, but merge is public through N-API and accepts any BlockStore. Mixing ecosystems can compare unrelated ordinals or panic when a numeric column is treated as bytes. Return an error before accessing either table.

🤖 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 `@packages/cli/src/block_store.rs` around lines 658 - 665, Update the merge
method containing the ecosystem assertion to enforce compatibility in all
builds: replace the debug-only check with a runtime discriminant comparison,
return the method’s existing error type on mismatch, and perform this validation
before accessing either table. Preserve the current merge behavior for matching
ecosystems.

Comment thread packages/cli/src/block_store.rs
Comment thread packages/envio/src/ChainState.res
Comment thread scenarios/test_codegen/test/rollback/Rollback_test.res

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +668 to +672
switch cs.blockStore->BlockStore.merge(
blockStore,
~fromBlock=Pervasives.max(knownHeight - cs.maxReorgDepth, 0),
~reportOnly=!cs.shouldRollbackOnReorg,
) {

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 Prune stale hash-only observations during merge

When a response page is merged here, fromBlock is only used as the comparison lower bound; rows below that threshold are still appended to cs.blockStore and are only removed later by applyBatchProgress. For sparse/RPC ranges with no parsedQueueItems, no batch progress runs, so each empty fetch can leave hash-only observations (for example the latest block and parent) in the store indefinitely, whereas the old ReorgDetection.registerReorgGuard rebuilt its map from only in-threshold rows on every response. This lets long no-event backfills or polling grow the block store unboundedly; drop/prune hash-only observations outside the current reorg threshold when merging reorg pages.

Useful? React with 👍 / 👎.

cs.blockStore->BlockStore.rollback(newProgressBlockNumber)
// A non-reorg chain's scanned hashes above the target are still valid, so
// keep them for reorg detection while the refetch repopulates the data.
cs.blockStore->BlockStore.rollback(newProgressBlockNumber, ~keepHashes=!isReorgChain)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Roll the reorg chain's hashes back to the valid block

When the rollback target checkpoint is below newProgressBlockNumber (for example there were no events/checkpoints in the intervening blocks), rolling the reorg chain's block store only to newProgressBlockNumber leaves orphaned hashes between the last valid block and that progress point. The next refetch can include parent/guard observations for those blocks and compare them against stale hashes, immediately reporting another reorg or choosing the wrong depth. For the reorg chain, clear hashes above rollbackTargetBlockNumber even if fetch/progress are restored to newProgressBlockNumber.

Useful? React with 👍 / 👎.

…org-tracking-2ucqi4

# Conflicts:
#	packages/envio/src/ChainState.res
#	packages/envio/src/sources/SimulateSource.res

@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: 52fa0321e7

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/envio/src/ChainState.res Outdated
~ecosystem=config.ecosystem.name,
~shouldChecksum=!lowercaseAddresses,
)
blockStore->BlockStore.merge(seedPage, ~fromBlock=0, ~reportOnly=false)->ignore

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 Preserve checkpoint hashes when seeding after rollback

When a restarted indexer has checkpoint rows from both before and after a prior rollback, chainReorgCheckpoints can contain the same block number with different hashes because the checkpoint query does not dedupe by block. BlockStore.fromJs records that as a within-page mismatch, and this merge(..., reportOnly=false) then returns the mismatch without appending any of the seed page; since the result is ignored, the chain resumes with no persisted reorg hashes at all until new pages rebuild them. Deduplicate/order the checkpoints before building the page, or merge the seed in a mode that still preserves usable hashes.

Useful? React with 👍 / 👎.

Comment thread packages/cli/src/block_store.rs Outdated
Comment on lines +911 to +915
.pending_mismatch
.as_ref()
.is_none_or(|p| (key as i64) < p.block_number)
{
inner.pending_mismatch = Some(HashMismatch {

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 Retain page conflicts past the reorg threshold

When a page records more than one same-block conflict, this keeps only the lowest block before merge applies fromBlock. If the lowest conflict is below the reorg threshold but a higher conflict is inside it (for example conflicts at blocks 50 and 100 with fromBlock=75), merge filters out the saved block 50 conflict and the block 100 conflict has already been discarded, so the self-conflicting page is appended without reporting a reorg. Keep all pending conflicts or choose the lowest conflict after applying the threshold.

Useful? React with 👍 / 👎.

inner.table.rollback_keeping_field(target, self.hash_field())
}
Ok(target) => inner.table.rollback(target),
Err(_) => inner.table.clear(),

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 Keep hashes when preserving a pre-genesis rollback

When a non-reorg chain is rolled back alongside another chain to a checkpoint before its first block, newProgressBlockNumber can be -1; with keepHashes=true the intent is to reduce all buffered rows to hash-only rows, but this branch clears the whole store instead. That drops valid in-threshold hashes for the non-reorg chain and creates a reorg-detection gap until those blocks are fetched again; handle negative targets specially when keep_hashes is set.

Useful? React with 👍 / 👎.

* Validate block-store responses before retry

* Address block hash query review feedback

* Track SVM cursor coverage in block stores

* Fix SVM parent validation and metrics
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@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: 0db045d918

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +214 to +216
if !shouldSwitch {
await Utils.delay(backoffMillis)
}

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 Keep the inconsistent-response backoff without an alternative

When this source is the only usable source, or all alternatives are still inside recoveryTimeout, setting lastFailedAt does not prevent the next loop from selecting it again because getNextSources falls back to primaries that are still in recovery. This branch then skips the delay on every even retry, so repeated internally inconsistent responses immediately reissue the same request against the same drifting provider instead of backing off; keep the delay when no alternative source is actually available.

Useful? React with 👍 / 👎.

retryRef := retryRef.contents + 1

| Source.InconsistentResponse(_) as err => {
await retryInconsistentResponse(

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 Reset source state before sleeping on inconsistent pages

When an RPC-backed source returns an internally inconsistent page, its onReorg hook is what clears cached block/transaction/receipt loaders. Because this await runs before invoking that hook, any other partition that runs during the 100ms/backoff window can still read orphaned cached data from the same source; invalidate the source state before waiting to retry.

Useful? React with 👍 / 👎.

claude added 2 commits July 16, 2026 15:18
…org-tracking-2ucqi4

# Conflicts:
#	packages/envio/src/SimulateItems.res
#	packages/envio/src/sources/SimulateSource.res
…g-2ucqi4' into claude/block-store-reorg-tracking-2ucqi4

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

🧹 Nitpick comments (1)
packages/envio/src/sources/HyperSyncClient.res (1)

305-306: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove these implementation-location comments.

  • packages/envio/src/sources/HyperSyncClient.res#L305-L306: remove the comment; the method signature already expresses the boundary.
  • packages/envio/src/sources/SvmHyperSyncClient.res#L208-L209: remove the equivalent comment.

As per coding guidelines, .res comments must not restate code or point to where behavior is defined.

🤖 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 `@packages/envio/src/sources/HyperSyncClient.res` around lines 305 - 306,
Remove the implementation-location comment near the relevant method in
packages/envio/src/sources/HyperSyncClient.res (lines 305-306) and remove the
equivalent comment in packages/envio/src/sources/SvmHyperSyncClient.res (lines
208-209); leave both method implementations unchanged.

Source: Coding guidelines

🤖 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 `@packages/cli/src/evm_hypersync_source/mod.rs`:
- Around line 177-180: Update the EVM source polling loop around the
next_block/cursor check to return the same structured failure used by the SVM
path when next_block does not advance beyond cursor. Remove the
sleep-and-continue retry behavior for this stalled response so the source
manager receives an error instead of growing request_stats indefinitely.

In `@packages/envio/src/sources/HyperSync.res`:
- Around line 1-17: The rate-limit exception mapping currently discards native
request statistics. Update Source.res lines 112-113 to extend rateLimited with
the requestStats payload, then update mapRateLimitedExn in
packages/envio/src/sources/HyperSync.res lines 1-17 to pass failure.requestStats
when constructing Source.RateLimited, preserving those timings for retry
metrics.

In `@scenarios/test_codegen/test/helpers/RpcSourcePins.res`:
- Around line 1-3: In scenarios/test_codegen/test/helpers/RpcSourcePins.res
lines 1-3, remove the module-purpose comment; at lines 85-87, replace the
migration narrative with only the invariant that BlockStore keys hashes by block
number, making the projection deduplicated and ascending.

In `@scenarios/test_codegen/test/RateLimit_test.res`:
- Around line 107-110: Replace the multiple field-level assertions with one
whole-value assertion per test. In
scenarios/test_codegen/test/RateLimit_test.res lines 107-110 and 125-126,
combine the hash-range and wait-time results; in
scenarios/test_codegen/test/SvmHyperSyncSource_test.res lines 208-209, combine
forwarded blocks and request statistics; in
scenarios/test_codegen/test/lib_tests/SourceManager_test.res lines 101-107,
combine projected timing and mapped reset delay; and in lines 1489-1504, combine
pre/post-reorg state with the final response. Build a single record or tuple
containing each expected value and compare it once in each affected test.

---

Nitpick comments:
In `@packages/envio/src/sources/HyperSyncClient.res`:
- Around line 305-306: Remove the implementation-location comment near the
relevant method in packages/envio/src/sources/HyperSyncClient.res (lines
305-306) and remove the equivalent comment in
packages/envio/src/sources/SvmHyperSyncClient.res (lines 208-209); leave both
method implementations unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6648dca-d679-4287-8e3c-386456625565

📥 Commits

Reviewing files that changed from the base of the PR and between 52fa032 and 85a1930.

📒 Files selected for processing (39)
  • packages/cli/src/block_store.rs
  • packages/cli/src/evm_hypersync_source/mod.rs
  • packages/cli/src/evm_rpc_source/mod.rs
  • packages/cli/src/field_table.rs
  • packages/cli/src/lib.rs
  • packages/cli/src/request_stats.rs
  • packages/cli/src/svm_hypersync_source/mod.rs
  • packages/cli/src/svm_hypersync_source/query.rs
  • packages/envio/src/ChainFetching.res
  • packages/envio/src/ChainState.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/Core.res
  • packages/envio/src/ReorgDetection.res
  • packages/envio/src/Rollback.res
  • packages/envio/src/SimulateItems.res
  • packages/envio/src/sources/BlockStore.res
  • packages/envio/src/sources/HyperSync.res
  • packages/envio/src/sources/HyperSync.resi
  • packages/envio/src/sources/HyperSyncClient.res
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/sources/SimulateSource.res
  • packages/envio/src/sources/Source.res
  • packages/envio/src/sources/SourceManager.res
  • packages/envio/src/sources/SourceManager.resi
  • packages/envio/src/sources/SvmHyperSyncClient.res
  • packages/envio/src/sources/SvmHyperSyncSource.res
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/RateLimit_test.res
  • scenarios/test_codegen/test/ReorgDetection_test.res
  • scenarios/test_codegen/test/RpcSourceContract_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • scenarios/test_codegen/test/SvmHyperSyncSource_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/helpers/RpcSourcePins.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
  • scenarios/test_codegen/test/rollback/ChainMocking.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/envio/src/Core.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • packages/envio/src/SimulateItems.res
  • packages/envio/src/Rollback.res
  • scenarios/test_codegen/test/IndexerState_test.res
  • packages/envio/src/sources/SimulateSource.res
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/ChainFetching.res
  • packages/envio/src/sources/BlockStore.res
  • scenarios/test_codegen/test/ReorgDetection_test.res
  • packages/envio/src/ChainState.res
  • packages/cli/src/block_store.rs
  • scenarios/test_codegen/test/rollback/Rollback_test.res

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/envio/src/sources/HyperSyncClient.res (1)

305-306: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove these implementation-location comments.

  • packages/envio/src/sources/HyperSyncClient.res#L305-L306: remove the comment; the method signature already expresses the boundary.
  • packages/envio/src/sources/SvmHyperSyncClient.res#L208-L209: remove the equivalent comment.

As per coding guidelines, .res comments must not restate code or point to where behavior is defined.

🤖 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 `@packages/envio/src/sources/HyperSyncClient.res` around lines 305 - 306,
Remove the implementation-location comment near the relevant method in
packages/envio/src/sources/HyperSyncClient.res (lines 305-306) and remove the
equivalent comment in packages/envio/src/sources/SvmHyperSyncClient.res (lines
208-209); leave both method implementations unchanged.

Source: Coding guidelines

🤖 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 `@packages/cli/src/evm_hypersync_source/mod.rs`:
- Around line 177-180: Update the EVM source polling loop around the
next_block/cursor check to return the same structured failure used by the SVM
path when next_block does not advance beyond cursor. Remove the
sleep-and-continue retry behavior for this stalled response so the source
manager receives an error instead of growing request_stats indefinitely.

In `@packages/envio/src/sources/HyperSync.res`:
- Around line 1-17: The rate-limit exception mapping currently discards native
request statistics. Update Source.res lines 112-113 to extend rateLimited with
the requestStats payload, then update mapRateLimitedExn in
packages/envio/src/sources/HyperSync.res lines 1-17 to pass failure.requestStats
when constructing Source.RateLimited, preserving those timings for retry
metrics.

In `@scenarios/test_codegen/test/helpers/RpcSourcePins.res`:
- Around line 1-3: In scenarios/test_codegen/test/helpers/RpcSourcePins.res
lines 1-3, remove the module-purpose comment; at lines 85-87, replace the
migration narrative with only the invariant that BlockStore keys hashes by block
number, making the projection deduplicated and ascending.

In `@scenarios/test_codegen/test/RateLimit_test.res`:
- Around line 107-110: Replace the multiple field-level assertions with one
whole-value assertion per test. In
scenarios/test_codegen/test/RateLimit_test.res lines 107-110 and 125-126,
combine the hash-range and wait-time results; in
scenarios/test_codegen/test/SvmHyperSyncSource_test.res lines 208-209, combine
forwarded blocks and request statistics; in
scenarios/test_codegen/test/lib_tests/SourceManager_test.res lines 101-107,
combine projected timing and mapped reset delay; and in lines 1489-1504, combine
pre/post-reorg state with the final response. Build a single record or tuple
containing each expected value and compare it once in each affected test.

---

Nitpick comments:
In `@packages/envio/src/sources/HyperSyncClient.res`:
- Around line 305-306: Remove the implementation-location comment near the
relevant method in packages/envio/src/sources/HyperSyncClient.res (lines
305-306) and remove the equivalent comment in
packages/envio/src/sources/SvmHyperSyncClient.res (lines 208-209); leave both
method implementations unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6648dca-d679-4287-8e3c-386456625565

📥 Commits

Reviewing files that changed from the base of the PR and between 52fa032 and 85a1930.

📒 Files selected for processing (39)
  • packages/cli/src/block_store.rs
  • packages/cli/src/evm_hypersync_source/mod.rs
  • packages/cli/src/evm_rpc_source/mod.rs
  • packages/cli/src/field_table.rs
  • packages/cli/src/lib.rs
  • packages/cli/src/request_stats.rs
  • packages/cli/src/svm_hypersync_source/mod.rs
  • packages/cli/src/svm_hypersync_source/query.rs
  • packages/envio/src/ChainFetching.res
  • packages/envio/src/ChainState.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/Core.res
  • packages/envio/src/ReorgDetection.res
  • packages/envio/src/Rollback.res
  • packages/envio/src/SimulateItems.res
  • packages/envio/src/sources/BlockStore.res
  • packages/envio/src/sources/HyperSync.res
  • packages/envio/src/sources/HyperSync.resi
  • packages/envio/src/sources/HyperSyncClient.res
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/sources/SimulateSource.res
  • packages/envio/src/sources/Source.res
  • packages/envio/src/sources/SourceManager.res
  • packages/envio/src/sources/SourceManager.resi
  • packages/envio/src/sources/SvmHyperSyncClient.res
  • packages/envio/src/sources/SvmHyperSyncSource.res
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/RateLimit_test.res
  • scenarios/test_codegen/test/ReorgDetection_test.res
  • scenarios/test_codegen/test/RpcSourceContract_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • scenarios/test_codegen/test/SvmHyperSyncSource_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/helpers/RpcSourcePins.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
  • scenarios/test_codegen/test/rollback/ChainMocking.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/envio/src/Core.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • packages/envio/src/SimulateItems.res
  • packages/envio/src/Rollback.res
  • scenarios/test_codegen/test/IndexerState_test.res
  • packages/envio/src/sources/SimulateSource.res
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/ChainFetching.res
  • packages/envio/src/sources/BlockStore.res
  • scenarios/test_codegen/test/ReorgDetection_test.res
  • packages/envio/src/ChainState.res
  • packages/cli/src/block_store.rs
  • scenarios/test_codegen/test/rollback/Rollback_test.res
🛑 Comments failed to post (4)
packages/cli/src/evm_hypersync_source/mod.rs (1)

177-180: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop retrying an unadvanced EVM cursor indefinitely.

A stalled backend response keeps this loop alive forever and continuously grows request_stats; the source manager never receives an error to retry. Return a structured failure here, as the SVM path does.

Proposed fix
 if next_block <= cursor {
-    tokio::time::sleep(Duration::from_millis(100)).await;
-    continue;
+    let error = map_err(anyhow::anyhow!(
+        "EVM block hash query made no progress: cursor={cursor}, next_block={next_block}"
+    ));
+    return Err(error_with_request_stats(error, &request_stats));
 }
📝 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.

            if next_block <= cursor {
                let error = map_err(anyhow::anyhow!(
                    "EVM block hash query made no progress: cursor={cursor}, next_block={next_block}"
                ));
                return Err(error_with_request_stats(error, &request_stats));
            }
🤖 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 `@packages/cli/src/evm_hypersync_source/mod.rs` around lines 177 - 180, Update
the EVM source polling loop around the next_block/cursor check to return the
same structured failure used by the SVM path when next_block does not advance
beyond cursor. Remove the sleep-and-continue retry behavior for this stalled
response so the source manager receives an error instead of growing
request_stats indefinitely.
packages/envio/src/sources/HyperSync.res (1)

1-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve native request timings through rate-limit retries.

The native payload includes requestStats, but the ReScript exception mapping drops them before SourceManager can update its metrics.

  • packages/envio/src/sources/HyperSync.res#L1-L17: propagate failure.requestStats when constructing the rate-limit exception.
  • packages/envio/src/sources/Source.res#L112-L113: extend rateLimited to carry those statistics so the retry handler can record them.
📍 Affects 2 files
  • packages/envio/src/sources/HyperSync.res#L1-L17 (this comment)
  • packages/envio/src/sources/Source.res#L112-L113
🤖 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 `@packages/envio/src/sources/HyperSync.res` around lines 1 - 17, The rate-limit
exception mapping currently discards native request statistics. Update
Source.res lines 112-113 to extend rateLimited with the requestStats payload,
then update mapRateLimitedExn in packages/envio/src/sources/HyperSync.res lines
1-17 to pass failure.requestStats when constructing Source.RateLimited,
preserving those timings for retry metrics.
scenarios/test_codegen/test/helpers/RpcSourcePins.res (1)

1-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove refactor narration from comments.

  • scenarios/test_codegen/test/helpers/RpcSourcePins.res#L1-L3: remove the module-purpose comment.
  • scenarios/test_codegen/test/helpers/RpcSourcePins.res#L85-L87: replace the migration narrative with only the invariant: BlockStore keys hashes by block number, so the projection is deduplicated and ascending.

As per coding guidelines, .res comments must not restate module purpose or narrate refactors.

📍 Affects 1 file
  • scenarios/test_codegen/test/helpers/RpcSourcePins.res#L1-L3 (this comment)
  • scenarios/test_codegen/test/helpers/RpcSourcePins.res#L85-L87
🤖 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 `@scenarios/test_codegen/test/helpers/RpcSourcePins.res` around lines 1 - 3, In
scenarios/test_codegen/test/helpers/RpcSourcePins.res lines 1-3, remove the
module-purpose comment; at lines 85-87, replace the migration narrative with
only the invariant that BlockStore keys hashes by block number, making the
projection deduplicated and ascending.

Source: Coding guidelines

scenarios/test_codegen/test/RateLimit_test.res (1)

107-110: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use one whole-value assertion per test.

  • scenarios/test_codegen/test/RateLimit_test.res#L107-L110: combine hash-range and wait-time checks into one record assertion.
  • scenarios/test_codegen/test/RateLimit_test.res#L125-L126: combine hash-range and zero-wait checks.
  • scenarios/test_codegen/test/SvmHyperSyncSource_test.res#L208-L209: assert forwarded blocks and request stats together.
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res#L101-L107: project timing and mapped reset delay into one result.
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res#L1489-L1504: collect pre/post-reorg state and final response into one assertion.

As per coding guidelines, **/*_test.res: “Always use single assert to check the whole value instead of multiple asserts for every field.”

📍 Affects 3 files
  • scenarios/test_codegen/test/RateLimit_test.res#L107-L110 (this comment)
  • scenarios/test_codegen/test/RateLimit_test.res#L125-L126
  • scenarios/test_codegen/test/SvmHyperSyncSource_test.res#L208-L209
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res#L101-L107
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res#L1489-L1504
🤖 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 `@scenarios/test_codegen/test/RateLimit_test.res` around lines 107 - 110,
Replace the multiple field-level assertions with one whole-value assertion per
test. In scenarios/test_codegen/test/RateLimit_test.res lines 107-110 and
125-126, combine the hash-range and wait-time results; in
scenarios/test_codegen/test/SvmHyperSyncSource_test.res lines 208-209, combine
forwarded blocks and request statistics; in
scenarios/test_codegen/test/lib_tests/SourceManager_test.res lines 101-107,
combine projected timing and mapped reset delay; and in lines 1489-1504, combine
pre/post-reorg state with the final response. Build a single record or tuple
containing each expected value and compare it once in each affected test.

Source: Coding guidelines

…ilience, seam validation (#1442)

* Address review findings: threshold authority, EVM parent-link check, test coverage

- Use the resumed-from-DB maxReorgDepth and per-chain shouldRollbackOnReorg
  everywhere (Batch checkpoints, getHighestBlockBelowThreshold, reorg
  logging/rollback decision) instead of mixing them with config values
- Validate EVM parent links in response stores: block N's parentHash must
  match block N-1's hash, within a page and across page seams; select
  parentHash in the EVM getBlockHashes re-fetch (Fuel has no parent-id
  field, so the check stays EVM/SVM-only)
- Make shouldRollbackOnReorg/maxReorgDepth required in ChainState.make
- Cover ChainState threshold arithmetic (depth changes across restarts,
  clamping, registerReorgGuard boundary) and applyBatchProgress hash
  retention with new tests
- Harden field_table: hard width checks on fixed columns, 64-field cap
- Drop stale ReorgDetection comments, dedupe native-failure unpacking,
  map rate-limited errors in the SVM getBlockHashes path too, fix test
  indentation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Surface EVM block-hash no-progress as an error for SourceManager to retry

Matches the SVM path: instead of silently sleeping 100ms in-process
(unbounded, no logging, no failover), the paginator returns a
RequestFailed error carrying the accumulated request stats. SourceManager
logs each retry with backoff and switches to another source on repeated
failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Friendlier no-progress message and a fast first retry for block-hash fetches

The replica-drift error now explains itself (routing to a replica slightly
behind the head, safe to continue after a retry), and SourceManager's
first block-hash retry backs off only 100ms to match how quickly a lagging
replica usually catches up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Polish block-hash retry: doubling backoff, message-first logging

- Backoff doubles from 100ms (capped at 60s) instead of stepping by 1s
- The native failure's own message becomes the retry log's msg (the
  replica-drift text is self-explanatory); generic failures keep the
  err payload
- Native failure causes are plain JS errors now, so logs no longer show
  a NativeRequestFailed wrapper
- Drop the logType field from the block-hash query logger and reword the
  replica-drift message

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Shorten the replica-drift message

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Use the resumed reorg depth for the pre-threshold fetch lag

Codex review: with a reduced configured depth, blockLag from the config
value would let fetching enter the stored rollback window without
history while detection still compares the resumed window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Fail test runs with the source error instead of retrying forever

In production a failing source is retried indefinitely (backoff and
failover keep the indexer alive), which in tests turns an unreachable
endpoint into a bare test-runner timeout with no context. SourceManager
now accepts a maxRetries cap (ENVIO_MAX_SOURCE_RETRIES) enforced across
the height, getItems, and getBlockHashes retry loops; when exhausted the
run fails with the underlying error. The test indexer worker defaults
the cap to 1, and generated templates give vitest 60s so the real error
surfaces before the runner's axe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Make live-endpoint tests resilient to hung connections

The test indexer worker now caps the HyperSync request timeout at 10s
(production default is 120s, which outlives every test timeout and turns
a hung connection into a context-free failure) so a hang fails fast and
retries on a fresh connection. The scenarios suite retries a failed test
once on CI - tests run sequentially with a fresh indexer per test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Reject EVM block-hash pages that don't cover their range densely

Codex review: the parent-link check skips absent neighbours (event pages
are sparse by design), so an include_all_blocks page omitting an interior
block or a parentHash could hide a mixed-fork seam. Each page is now
validated to carry every block in its covered range with hash and parent
hash before it joins the aggregate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Honor the retry cap in the subscription fallback poller

Codex review: the stale-subscription fallback swallowed every
getHeightOrThrow failure, so with a subscription installed the cap never
fired and a dead endpoint could still hang a test run. The fallback's
rejection is separately observed since it can lose the height race.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Retry a template smoke test once before failing the job

The template suites index real blocks through live HyperSync; one hung
connection on the runner shouldn't fail the job now that a failed
attempt surfaces quickly with a real error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Retry e2e smoke tests once on CI

Same rationale as the scenarios suite: the smoke tests hit live
HyperSync, and a hung runner connection now fails fast with a real
error, so a single retry absorbs it without hiding deterministic
failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Replace the global CI retry with per-test retries on live-endpoint tests

Only the tests that hit live HyperSync/RPC endpoints retry (following the
existing SourceBlockHashes pattern of {retry: 3}): the HyperSync client
live tests, the RPC height check, the createTestIndexer tests that fetch
mainnet, and the e2e smoke test. Deterministic tests fail on the first
attempt again. The Vitest binding options gained a timeout field so the
corrupted-token test keeps its 60s budget alongside the retry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Keep the test-worker HyperSync timeout at 30s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Widen template harness timeout padding from 10s to 30s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Simplify test-run resilience knobs

Drop the template harness re-run hack and the derived outer timeout (the
config value is the single budget now), run template suites with a 30s
per-test vitest timeout, and rely on ENVIO_MAX_SOURCE_RETRIES=3 in test
workers instead of overriding the HyperSync client timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

* Replace parent-hash validation with hash-collision checks

A single HyperSync response is internally consistent, so parent-link
validation only guarded the seams between paginated block-hash requests.
Those seams are now covered directly: each follow-up page re-requests the
last returned block, and a fork switch between requests surfaces as a
hash collision on the overlapping block via the existing duplicate
detection. The EVM/SVM parent-link checks, the dense-range validation,
and the parent fields in block-hash queries are gone.

RPC responses can mix forks since every block is fetched separately, so
getBlockHashes now derives a minimal (number-1, parentHash) row from each
block - like the items path already did - letting the page's existing
collision check cross-validate separately fetched blocks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RcUijSCoEY19MCEbdh6fL

---------

Co-authored-by: Claude <noreply@anthropic.com>

@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: 20a8fc4066

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +259 to +260
cursor = next_slot;
overlap_slot = last_slot.or(overlap_slot);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reset the overlap anchor after an empty SVM page

When a paginated SVM block-hash request advances through a range containing only skipped slots, last_slot is None but overlap_slot retains the last block from an earlier page. The next request then starts at that old slot rather than cursor; once it receives the same cursor boundary again, next_slot <= cursor treats normal progress as a replica failure and retries indefinitely. Clear the overlap anchor when the current page has no block, while retaining it only for the immediately preceding page.

Useful? React with 👍 / 👎.

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

🧹 Nitpick comments (1)
scenarios/test_codegen/test/SvmHyperSyncSource_test.res (1)

208-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use one assertion for the complete response contract.

Combine the forwarded range and request statistics into a single expected value.

Suggested adjustment
-    t.expect(capturedBlockHashRequests->Utils.Array.lastUnsafe).toEqual(blockNumbers)
-    t.expect(response.requestStats).toEqual([{Source.method: "getBlockHashes", seconds: 0.25}])
+    t.expect({
+      "blockNumbers": capturedBlockHashRequests->Utils.Array.lastUnsafe,
+      "requestStats": response.requestStats,
+    }).toEqual({
+      "blockNumbers": blockNumbers,
+      "requestStats": [{Source.method: "getBlockHashes", seconds: 0.25}],
+    })

As per coding guidelines, **/*_test.res: Always use single assert to check the whole value instead of multiple asserts for every field.

🤖 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 `@scenarios/test_codegen/test/SvmHyperSyncSource_test.res` around lines 208 -
209, Update the test assertions around the captured block hash request and
response statistics to use one assertion against the complete expected response
contract. Combine the forwarded blockNumbers range and requestStats into the
single expected value, removing the separate field-level assertions while
preserving both expected values.

Source: Coding guidelines

🤖 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 `@packages/e2e-tests/src/template-tests/templates.test.ts`:
- Line 191: Update the enclosing test timeout in the template test around the
child process invocation to exceed config.timeouts.test by a settling buffer,
such as 30 seconds. Keep the child process timeout unchanged, and apply the
cushioned deadline only to the outer test.

---

Nitpick comments:
In `@scenarios/test_codegen/test/SvmHyperSyncSource_test.res`:
- Around line 208-209: Update the test assertions around the captured block hash
request and response statistics to use one assertion against the complete
expected response contract. Combine the forwarded blockNumbers range and
requestStats into the single expected value, removing the separate field-level
assertions while preserving both expected values.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd3e85ea-17af-4ac5-996b-5d084157e97d

📥 Commits

Reviewing files that changed from the base of the PR and between 85a1930 and 20a8fc4.

📒 Files selected for processing (32)
  • packages/cli/src/block_store.rs
  • packages/cli/src/evm_hypersync_source/mod.rs
  • packages/cli/src/field_table.rs
  • packages/cli/src/svm_hypersync_source/mod.rs
  • packages/cli/templates/dynamic/init_templates/shared/package.json.hbs
  • packages/e2e-tests/src/template-tests/templates.test.ts
  • packages/envio/src/Batch.res
  • packages/envio/src/ChainFetching.res
  • packages/envio/src/ChainState.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/Env.res
  • packages/envio/src/ReorgDetection.res
  • packages/envio/src/TestIndexer.res
  • packages/envio/src/bindings/Vitest.res
  • packages/envio/src/sources/HyperSync.res
  • packages/envio/src/sources/HyperSync.resi
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/sources/Source.res
  • packages/envio/src/sources/SourceManager.res
  • packages/envio/src/sources/SourceManager.resi
  • packages/envio/src/sources/SvmHyperSyncSource.res
  • scenarios/e2e_test/src/indexer.test.ts
  • scenarios/test_codegen/test/EventHandler.test.ts
  • scenarios/test_codegen/test/HyperSyncClient_test.res
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/ReorgDetection_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • scenarios/test_codegen/test/SvmHyperSyncSource_test.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
💤 Files with no reviewable changes (1)
  • packages/envio/src/ReorgDetection.res
🚧 Files skipped from review as they are similar to previous changes (20)
  • packages/envio/src/sources/SourceManager.resi
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/sources/SvmHyperSyncSource.res
  • scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • packages/envio/src/Batch.res
  • packages/envio/src/sources/RpcSource.res
  • packages/envio/src/sources/HyperSync.res
  • packages/envio/src/ChainFetching.res
  • packages/cli/src/field_table.rs
  • packages/envio/src/sources/Source.res
  • packages/cli/src/evm_hypersync_source/mod.rs
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/cli/src/svm_hypersync_source/mod.rs
  • packages/envio/src/ChainState.res
  • packages/cli/src/block_store.rs
  • packages/envio/src/sources/HyperSync.resi


expect(result.exitCode, `[${name}] test failed (exit ${result.exitCode}):\n${result.stderr}\n${result.stdout}`).toBe(0);
}, config.timeouts.test + 10_000);
}, config.timeouts.test);

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep a timeout cushion for the pnpm test command.

The child process already has config.timeouts.test at Line 187, but the enclosing test now has the same deadline. Restore a buffer, such as config.timeouts.test + 30_000, so the command can time out and settle cleanly before Vitest aborts the test.

🤖 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 `@packages/e2e-tests/src/template-tests/templates.test.ts` at line 191, Update
the enclosing test timeout in the template test around the child process
invocation to exceed config.timeouts.test by a settling buffer, such as 30
seconds. Keep the child process timeout unchanged, and apply the cushioned
deadline only to the outer test.

claude added 3 commits July 17, 2026 14:18
…org-tracking-2ucqi4

# Conflicts:
#	packages/envio/src/ChainState.res
#	scenarios/test_codegen/test/rollback/Rollback_test.res
…g-2ucqi4' into claude/block-store-reorg-tracking-2ucqi4
The review-findings commit made ~shouldRollbackOnReorg/~maxReorgDepth
required; the CrossChainState_test call site introduced by the query-sizing
merge needed ~shouldRollbackOnReorg added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aj6SS9KbG9mytdzuYnMs3a

@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.

Caution

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

⚠️ Outside diff range comments (2)
packages/envio/src/ChainState.res (2)

573-581: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clamp an alignment-capped range cost before deriving the item budget.

maxTargetBlock can clamp chainTargetBlock below bufferBlockNumber. That makes rangeCost negative and can pass a negative chainTargetItems value into FetchState.getNextQuery.

Proposed fix
-    let rangeCost =
-      density *. (chainTargetBlock - cs.fetchState->FetchState.bufferBlockNumber)->Int.toFloat
+    let remainingBlocks = Pervasives.max(
+      0,
+      chainTargetBlock - cs.fetchState->FetchState.bufferBlockNumber,
+    )
+    let rangeCost = density *. remainingBlocks->Int.toFloat
     Pervasives.min(chainTargetItems, Math.ceil(rangeCost) +. cs.pendingBudget)
🤖 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 `@packages/envio/src/ChainState.res` around lines 573 - 581, Clamp the computed
range cost in the effective-density branch of the chain target calculation to
zero before applying Math.ceil and cs.pendingBudget. Preserve the existing
minimum with chainTargetItems, ensuring FetchState.getNextQuery never receives a
negative item budget when chainTargetBlock is below bufferBlockNumber.

528-536: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not report an unfetched one-block range as complete.

When upper === lower, this returns 1. even at the documented initial startBlock - 1 frontier. That makes frontierProgress treat a single remaining block as fetched, so cross-chain alignment can deprioritize it prematurely. Return 0. until the frontier reaches that block; reserve unconditional 1. for an empty range.

Proposed fix
 let progressAtBlock = (cs: t, ~blockNumber) => {
   let (lower, upper) = cs->progressRange
-  upper <= lower
-    ? 1.
-    : Pervasives.max(
+  if upper < lower {
+    1.
+  } else if upper === lower {
+    blockNumber >= upper ? 1. : 0.
+  } else {
+    Pervasives.max(
         0.,
         Pervasives.min(1., (blockNumber - lower)->Int.toFloat /. (upper - lower)->Int.toFloat),
       )
+  }
 }
🤖 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 `@packages/envio/src/ChainState.res` around lines 528 - 536, Update
progressAtBlock so an equal lower/upper range returns 0. until blockNumber
reaches the remaining frontier block, then returns 1.; preserve the existing
clamped calculation for non-empty ranges and reserve unconditional 1. for an
actually empty range.
🤖 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.

Outside diff comments:
In `@packages/envio/src/ChainState.res`:
- Around line 573-581: Clamp the computed range cost in the effective-density
branch of the chain target calculation to zero before applying Math.ceil and
cs.pendingBudget. Preserve the existing minimum with chainTargetItems, ensuring
FetchState.getNextQuery never receives a negative item budget when
chainTargetBlock is below bufferBlockNumber.
- Around line 528-536: Update progressAtBlock so an equal lower/upper range
returns 0. until blockNumber reaches the remaining frontier block, then returns
1.; preserve the existing clamped calculation for non-empty ranges and reserve
unconditional 1. for an actually empty range.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29305cb3-4c27-4e8c-81d2-3ecd76a320db

📥 Commits

Reviewing files that changed from the base of the PR and between 20a8fc4 and 15a7183.

📒 Files selected for processing (9)
  • packages/envio/src/ChainState.res
  • packages/envio/src/ChainState.resi
  • packages/envio/src/sources/SourceManager.res
  • packages/envio/src/sources/SourceManager.resi
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res
💤 Files with no reviewable changes (1)
  • packages/envio/src/sources/SourceManager.resi
🚧 Files skipped from review as they are similar to previous changes (7)
  • scenarios/test_codegen/test/IndexerState_test.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • packages/envio/src/ChainState.resi
  • scenarios/test_codegen/test/lib_tests/CrossChainState_test.res
  • scenarios/test_codegen/test/lib_tests/SourceManager_test.res
  • packages/envio/src/sources/SourceManager.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res

…apshot, cleanups (#1448)

- field_table: extract shared row-reduction/drop helpers from
  prune_keeping_field and rollback_keeping_field.
- block_store: trim EvmBlockInput to the (number, timestamp, hash) trio the
  JS callers actually send, dropping the unreachable full-block conversion.
- block_store: store the EVM hash as a fixed 32-byte column again; fromJsEvm
  left-pads shorter mock markers into that width (a no-op for real 32-byte
  observations), so storage stays fixed-width without input-fixture churn.
- block_store: extract a ResponsePage type owning the response-only state
  (within-response conflict + SVM cursor coverage), so the persistent store no
  longer carries it and merge resets it explicitly instead of clearing fields.
- request_stats/Source: mark the native-failure envelope with an explicit
  ENVIO_NATIVE_FAILURE: prefix so ReScript decodes only our own payloads and
  never a coincidental JSON error message.
- Batch/ChainState: snapshot the in-threshold scanned hashes when the batch is
  assembled instead of reading the live block store per block; drops the live
  blockStore/maxReorgDepth off chainBeforeBatch and removes the per-block napi
  getHash calls from the checkpoint loop.
- tests: migrate the getHash-derived EVM hash expectations to the padded
  32-byte form via a shared MockIndexer.evmBlockHash helper.


Claude-Session: https://claude.ai/code/session_01K3yMGs2o2DbQqu6LSTcHEd

Co-authored-by: Claude <noreply@anthropic.com>
.transpose()
.context("convert rollback guard")
.map_err(map_err)?,
rollback_guard,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we don't need to return back rollback guard anymore since all the purpose it served is now done on the Rust side.

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +620 to +621
if cross.is_none() || report_only {
dst.table.append_from(&mut src.table);

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 Clear old fork hashes after report-only reorgs

When shouldRollbackOnReorg=false, this report_only merge overwrites only the hashes present in the current response, but it leaves any other in-threshold hashes from the old fork in the persistent store. The deleted ReorgDetection.registerReorgGuard reset its map after a detect-only reorg, so a partial page that detects a reorg at block N can now be followed by adjacent pages that compare against stale old-fork hashes and repeatedly log the same reorg at lower/other blocks. Clear the existing hash window (or otherwise reset detect-only state) when report_only is true and cross is Some(_).

Useful? React with 👍 / 👎.

Comment on lines +400 to +402
pub height: Option<i64>,
pub parent_slot: Option<i64>,
pub parent_hash: Option<String>,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we don't need these three.

// rolled back alongside another chain's reorg) blocks above the target are
// reduced to hash-only rows: their data is refetched, but the scanned hashes
// stay valid for reorg detection.
@send external rollback: (t, int, ~keepHashes: bool) => unit = "rollback"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should always drop hashes and all data when we roll back.

// Drop blocks above the given block (rolled back).
@send external rollback: (t, int) => unit = "rollback"
// Hash of a stored block, if the store still holds it.
@send external getHash: (t, int) => Null.t<string> = "getHash"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Check whether we don't have dead code in this file and others.

let backoffMillis = retry === 0 ? 100 : Pervasives.min(1000 * retry, 60_000)
let log = retry >= 2 ? Logging.childWarn : Logging.childTrace
logger->log({
"msg": `Source returned an internally inconsistent ${method} response. Retrying the complete request.`,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This error message is confusing for end user. Let's update it to something more friendly, saying that we received a partial indicator of a reorg from the source, retrying the request to better identify whether a reorg happened or not.

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