Skip to content

[indexer]: drain PendingStatusMetadata on every chain instance#929

Merged
Wizdave97 merged 8 commits into
mainfrom
debug-pending-status-flush
May 29, 2026
Merged

[indexer]: drain PendingStatusMetadata on every chain instance#929
Wizdave97 merged 8 commits into
mainfrom
debug-pending-status-flush

Conversation

@Wizdave97
Copy link
Copy Markdown
Member

Summary

  • The Hyperbridge-only block handler added in [indexer]: Drain PendingStatusMetadata via Hyperbridge block handler #926 returned 0 rows even
    though the PendingStatusMetadata table had 21 entries. Root cause:
    SubQuery multichain forces historical: 'timestamp'
    (@subql/node-core store.service.js:241-250) and the beforeFind
    hook injects WHERE __block_range @> <instance_timestamp> on every
    read. Each chain's indexer runs as its own instance with its own
    indexed timestamp, so rows written by chain B are invisible to chain A
    until A's indexed timestamp catches up to B's write timestamp. That
    also explains why the per-request flushPendingStatuses(commitment)
    inside createOrUpdate was silently missing rows: same blind spot.
  • Move handlePendingStatusFlush out of the {{#if isHyperbridgeChain}}
    guard in the substrate YAML template (modulo: 10, ~1 min on a 6s
    chain) so every substrate chain instance drains its own writes.
  • Add handlePendingStatusFlushEvm as kind: ethereum/BlockHandler on
    every EVM chain (modulo: 50) for the same reason — without it,
    rows written by EVM instances would only drain when their chain
    happens to next touch the row by commitment, which never happens.
  • Switch PendingStatusService.flushBatch to getByFields([], { limit })
    with on-row entityType dispatch instead of a per-entityType
    indexed lookup, plus structured logs (chain=, block=,
    fetched N, not yet present) for future post-mortem.

Wizdave97 added 8 commits May 28, 2026 18:25
Add entry/exit logs in handlePendingStatusFlush, per-entityType fetch
counts and parent-missing logs in PendingStatusService.flushBatch, and
a modulo: 10 filter on the BlockHandler so it fires every 10
Hyperbridge blocks (~1 min) for easier log inspection.
If the entityType index is stale or stores values that drift from our
constants the per-entityType fetch returns zero rows even when the DB
has matching pending entries. Switch to PendingStatusMetadata.getByFields([], { limit })
which runs a single un-filtered query and dispatch on each row's
entityType field after read. Unknown entityType values are warn-logged
and skipped.
SubQuery multichain forces historical='timestamp', so the beforeFind
hook attaches a __block_range @> <instance_timestamp> filter to every
read. Rows written by another chain's instance are invisible to the
Hyperbridge instance until Hyperbridge's indexed timestamp catches up
to the writing chain's timestamp — which is why the Hyperbridge-only
BlockHandler returned 0 rows despite the table having 21 entries.

Move handlePendingStatusFlush out of the isHyperbridgeChain guard and
add handlePendingStatusFlushEvm with kind: ethereum/BlockHandler so
each chain instance flushes the rows it itself wrote (those rows are
by construction in its own visible time range). Substrate modulo 10
(~1 min on 6s chains); EVM modulo 50 (chain-dependent, ~10 min on
mainnet). Log lines include chainId so cross-chain runs are easy to
distinguish.
The previous catch blocks used // @ts-ignore on the line above a
multi-line logger.error call, so the directive didn't reach the
error.message access on a later line and tsc failed with TS18046:
'error' is of type 'unknown'. Narrow with `error instanceof Error`
instead of suppressing.
Empty getByFields filter is technically valid (x-sequelize 1992-1998
returns "" for Op.notIn:[]) but yields no diagnostic signal when 0
rows come back. Switch to an indexed-field IN filter — entityType is
@index-ed — and log up to 3 sample rows (entityType@chain:id) so the
chain that actually wrote stuck rows is identifiable from the indexer
logs. Also lets us rule out empty-filter quirks definitively.
Add a temporary diagnostic block in flushBatch that probes one
known-existing BSC pending row (commitment
0x10bf3434...c447) via three different store APIs: get(id),
getByCommitment, and getByFields with '=' on entityType. Each takes a
different @subql/node-core code path; whichever returns the row tells
us where the bug is and which API to use as the fix. Also log
block.timestamp on the EVM handler so we can rule out catch-up lag
definitively. Will be reverted once the fix is identified.
Drop the diagnostic block + per-block entry/completed logs and per-row
'not yet present' info logs from the pending-status flush path. The
original per-request flushPendingStatuses(commitment) is the working
path; the new BlockHandlers stay as a no-op safety net (they log only
when a real row is found or materialized).

Also bump polkadot-hub-mainnet startBlock 16215883 → 16215884 and pin
unfinalizedBlocks: false.
…flush

# Conflicts:
#	sdk/packages/indexer/scripts/templates/substrate-chain.yaml.hbs
#	sdk/packages/indexer/src/handlers/events/pendingStatus/handlePendingStatusFlush.event.handler.ts
#	sdk/packages/indexer/src/services/pendingStatus.service.ts
@Wizdave97 Wizdave97 requested review from ddboy19912 and dharjeezy May 29, 2026 06:07
@Wizdave97 Wizdave97 merged commit ecfa02d into main May 29, 2026
3 of 6 checks passed
@Wizdave97 Wizdave97 deleted the debug-pending-status-flush branch May 29, 2026 06:37
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