fix(portal): emit per-chain metrics file on a 0-block window (#140)#142
Merged
Conversation
When a chain streams 0 blocks in a sync window, `syncBlockData` early-returns
at the `blockArr.length === 0` guard BEFORE the `writeMetrics(...)` call, so no
`portal-metrics.<chainId>` file was produced for that chain/window. Consumers
that expect one metrics file per configured chain (e.g. the keyless
euler-multichain e2e harness for base/arbitrum, whose factory-discovered events
fall outside the small default window) then fail with
`metrics file not found: portal-metrics.<chainId>`.
Indexing correctness is unaffected — this is purely an observability/gate-ability
gap. A 0-block chain still ran, and `blocks=0` is a meaningful, gate-able datapoint.
Fix: extract the `writeMetrics({...})` args into a single `emitMetrics()` closure
(one source of truth for the arg shape) and invoke it on BOTH `syncBlockData`
exits — the normal insert path AND the 0-block early return. `writeMetrics`
overwrites `${metricsFile}.${chain.id}` last-write-wins and reads the live
cumulative `stats`, so the 0-block file carries the same FROZEN schema with honest
zero counts. The early-out behavior (nothing to insert for 0 blocks) is preserved.
Regression test (portal/portal.test.ts): drives a Portal-served in-range window
that assembles 0 blocks and asserts the per-chain metrics file exists with the
zero-window schema. Mutation-verified: removing `emitMetrics()` from the
early-return branch (the old code) fails the test (file absent); with the fix it
passes. Both-version gates green (0.16.8 + 0.16.6: 16 files / 313 tests each).
Fixes #140
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #140.
Mechanism
syncBlockDataearly-returns at theblockArr.length === 0guard before thewriteMetrics(...)call, so a chain that streams 0 blocks in a sync window never writes itsportal-metrics.<chainId>file. Any consumer expecting one metrics file per configured chain then fails withmetrics file not found: portal-metrics.<chainId>.Where it shows up: running the keyless
euler-multichainexample,base(8453) andarbitrum(42161) stream 0 blocks in the small default window (their factory-discovered events fall outside it), so their metrics files are absent and the run is un-gate-able. All chains do backfill via the Portal (rpc_fallback=0) — only the metrics-file emission is skipped. Indexing correctness is unaffected; this is an observability/DX gap.Fix
Extract the
writeMetrics({...})args into a singleemitMetrics()closure — one source of truth for the arg shape — and invoke it on bothsyncBlockDataexits: the normal insert path and the 0-block early return.writeMetricsoverwrites${metricsFile}.${chain.id}(last-write-wins) and reads the live cumulativestats, so the 0-block file carries the same FROZEN schema with honest zero counts (inserted.blocks=0,rpcFallbackIntervals=0). The early-out (nothing to insert for 0 blocks) is preserved. No duplication of the arg object, so the two call sites cannot diverge.The
!spath (interval not Portal-served / RPC-delegated) is deliberately untouched — it did not write metrics before and still doesn't; only the "Portal served this interval but it assembled 0 blocks" branch is fixed, which is exactly the issue's case.Mutation-verified regression test
portal/portal.test.ts—regression (#140): a chain that streams 0 blocks in its window STILL emits a per-chain metrics file (blocks=0). Drives a Portal-served in-range window[0, 5000]that assembles 0 blocks (header-only anchor, no matched data), setsPORTAL_METRICS_FILE, runssyncBlockRangeData+syncBlockData, asserts the preconditioninserted.blocks == 0(proving the buggy early-return path is reached), then asserts the per-chain file exists with the zero-window schema.Mutation verification (in the grafted
0.16.8tree — my repo tree stayed clean):emitMetrics()from the early-return branch (the old code): test FAILS atexpect(existsSync(perChainFile)).toBe(true)→expected false to be true(file absent), vitest EXIT=1.Both-version gates (
scripts/sync-upstream.sh <ver> --test)biome check .clean (error-level, pinned@biomejs/biome@2.5.2).🤖 Generated with Claude Code