Skip to content

Fix/indexer parser hardening - #1075

Merged
Mimah97 merged 4 commits into
Vatix-Protocol:devfrom
janetpius-cmd:fix/indexer-parser-hardening
Aug 31, 2026
Merged

Fix/indexer parser hardening#1075
Mimah97 merged 4 commits into
Vatix-Protocol:devfrom
janetpius-cmd:fix/indexer-parser-hardening

Conversation

@janetpius-cmd

Copy link
Copy Markdown
  • Resolution parser (resolutionParser.ts): legacy dev-stub payload shapes (ScvVec tuple, legacy ScvMap) are now rejected with ResolutionParseError when NODE_ENV=production, instead of silently producing ResolutionCandidate rows with a blank oracleAddress. Emits indexer.parser.legacy_shape_rejected.
  • Trade parser (tradeParser.ts): buy_order_id/sell_order_id are now validated — always rejects empty ids, and in production requires a UUID shape matching Order.id, so a fill that can't join to a real CLOB order fails the batch instead of persisting unlinked. Emits indexer.parser.unjoinable_order_id.
  • Collateral deposited parser (collateralDepositedParser.ts): amountRaw is now validated against the same 7-decimal/Decimal(20,8) bounds decimalUtils.amountRawToDecimal uses elsewhere, rejects zero/negative amounts, and in production rejects amounts that decode as a plain number instead of the expected i128 bigint. Emits indexer.parser.invalid_collateral_scale.
  • Retry classification (retry.ts): added classifyError() distinguishing fatal (parse/validation errors, non-429 4xx — never retried), rate_limited (429 — backs off harder, honors Retry-After), and transient (network errors, 5xx — standard backoff). Fixes the "parse errors retried forever" gap.

Closes #1022
Closes #1023
Closes #1024
Closes #1025

resolutionParser silently accepted legacy ScvVec/ScvMap dev-stub shapes
(with a blank oracleAddress) in every environment, including production.
Gate those shapes behind NODE_ENV, fail fast with ResolutionParseError
and an indexer.parser.legacy_shape_rejected metric when they surface in
production, and keep accepting them for local/dev fixtures.

Claude-Session: https://claude.ai/code/session_01WR71DfNQdvTTWtuVDGBzD6
tradeParser cast buy_order_id/sell_order_id straight to String with no
validation, so a fill that could never join to a real Order row (empty,
or not UUID-shaped like Order.id) was persisted as if it were joined.
Reject empty order ids in every environment and non-UUID order ids in
production, with an indexer.parser.unjoinable_order_id metric.

Claude-Session: https://claude.ai/code/session_01WR71DfNQdvTTWtuVDGBzD6
collateralDepositedParser accepted any i128 bigint as amountRaw with no
bounds check, so a wrongly-scaled or out-of-range amount passed through
silently and only surfaced later (e.g. as a DB error far from the event
that caused it). Validate amountRaw against the same 7-decimal /
Decimal(20,8) bounds decimalUtils.amountRawToDecimal enforces elsewhere,
reject zero/negative amounts, and in production reject amounts that
decode as a plain number instead of the expected i128 bigint.

Claude-Session: https://claude.ai/code/session_01WR71DfNQdvTTWtuVDGBzD6
isTransientError() only recognized a fixed set of network error codes/
messages, so a 429 or 5xx from the Stellar RPC (which surfaces as an
HTTP-status-bearing error, not a NodeJS.ErrnoException) had no correct
retry path, and nothing stopped a re-thrown parse error from being
retried indefinitely if its message ever collided with a transient
string. Add classifyError() (fatal/rate_limited/transient), route
withRetry through it, back off harder (and honor Retry-After) on 429,
and never retry ResolutionParseError/TradeParseError/
CollateralDepositedParseError/MarketCreatedParseError/RetryValidationError.

Claude-Session: https://claude.ai/code/session_01WR71DfNQdvTTWtuVDGBzD6
@Mimah97
Mimah97 merged commit d8566fc into Vatix-Protocol:dev Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants