Skip to content

fix(tests): wait for archive indexing before asserting on emitted blocks - #205

Merged
SanabriaRusso merged 1 commit into
mainfrom
fix/archive-index-race
Jul 30, 2026
Merged

fix(tests): wait for archive indexing before asserting on emitted blocks#205
SanabriaRusso merged 1 commit into
mainfrom
fix/archive-index-race

Conversation

@SanabriaRusso

Copy link
Copy Markdown
Collaborator

Closes #204.

Problem

The live-network resolver tests query the archive immediately after pendingTx.wait() resolves. That wait is the daemon confirming inclusion — the archive node ingests the block into Postgres asynchronously afterward. When the query wins that race, events[events.length - 1] is still the previous suite's block, so the assertion reads the previous block's entry count.

This is what failed CI on #203 (run 30536209372): expected 3, actual 1, where the preceding suite had emitted exactly one event. A re-run of the identical commit passed, and #203 changed only a version string — see #204 for the full evidence.

Note that "the emits got split across blocks" was never possible: emitSingleEvent puts all numberOfEmits emits inside a single Mina.transaction (zkapp/utils.ts:106), so once included they are necessarily in one block.

Changes

tests/resolvers.test.ts — adds waitForNewBlock, plus emitAndQueryEvents / emitAndQueryActions wrappers, and routes all seven emit-then-query before() hooks through them (5 event suites, 2 action suites).

zkapp/utils.tssendTransaction and sendTransactions now rethrow after logging.

Design note: why the poll predicate is content-independent

waitForNewBlock waits for "a new block for this address is visible" — it captures the block count before emitting and polls until it increases. It deliberately does not poll until the count matches what the test expects.

Polling on the expected value would make these assertions tautological: the test would spin until it passed, and a genuine wrong-count regression would surface as an opaque 30s timeout instead of a clean expected 3, actual 2 diff. The bound is 30 attempts at 1s, and the timeout message states that the daemon already confirmed inclusion, so a failure points at archive lag rather than at the transaction.

Why rethrowing matters

sendTransaction previously swallowed wait() failures:

} catch (error) {
  console.error('Transaction rejected or failed to finalize:', error);
}

A genuinely rejected or timed-out transaction produced exactly the same stale-block count mismatch as the race above, several lines away from the real cause. In the #203 failure the absence of that log line is what proved the transaction had actually succeeded — but that only worked because someone read the log closely. Rethrowing surfaces it at the origin.

Verification

npm run build (tsc) and npm run lint clean. The behavioral proof is the Run-Tests job on this PR, which exercises these paths against mina-local-network.

Since this fixes a race, a single green run is evidence but not proof. The change is structural — there is no longer a query that can observe pre-ingestion state — rather than a timing tweak that widens a window.

Not addressed here

The NetworkState suite (tests/resolvers.test.ts:261-308) has a related weakness: a hard-coded setTimeout(25000) and a direct archive-height vs daemon-height equality assertion that is racy in the same way. It is currently passing and fixing it would widen this diff considerably, so it is left for a follow-up — noted in #204.

🤖 Generated with Claude Code

The live-network resolver tests queried the archive immediately after
`pendingTx.wait()` resolved. That wait is the *daemon* confirming
inclusion; the archive node ingests the block into Postgres
asynchronously afterward. When the query won that race, the "latest
block" was still the previous suite's block, so the assertion saw the
previous block's entry count.

This is what failed CI on PR #203 (expected 3, actual 1) — the previous
suite emitted exactly one event. All emits for a given suite go into a
single transaction, so a genuine split across blocks was never possible.

Adds `waitForNewBlock`, plus `emitAndQueryEvents` / `emitAndQueryActions`
wrappers, and routes all seven emit-then-query hooks through them.

The poll predicate is deliberately content-independent: it waits for "a
new block for this address is visible", not "the count equals what the
test expects". Polling on the expected value would make the assertions
tautological and would convert a real wrong-count regression into an
opaque timeout instead of a clean assertion diff.

Also rethrows in `sendTransaction`/`sendTransactions` after logging. The
swallowed error meant a genuinely rejected transaction surfaced as the
same confusing stale-block count mismatch, far from its cause.

Closes #204

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SanabriaRusso SanabriaRusso self-assigned this Jul 30, 2026
@SanabriaRusso SanabriaRusso added the bug Something isn't working label Jul 30, 2026
@SanabriaRusso
SanabriaRusso merged commit 6d4f3b5 into main Jul 30, 2026
6 checks passed
@SanabriaRusso
SanabriaRusso deleted the fix/archive-index-race branch July 30, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flaky live-network tests: resolvers query the archive before it has indexed the emitted block

1 participant