Skip to content

fix(indexer-freshness): handle unavailable block timestamps explicitly - #468

Merged
spalen0 merged 3 commits into
mainfrom
iss-465
Nov 8, 2026
Merged

spalen0 merged 3 commits into
mainfrom
iss-465

Conversation

@spalen0

@spalen0 spalen0 commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #465

Problem

On 2026-11-07 14:05 UTC the freshness check logged Failed to fetch block 47317489 timestamp on KATANA: 'NoneType' object is not subscriptable. The raw eth_getBlockByNumber call returns the JSON-RPC body as is, so a null block or an RPC error never raises inside make_request. Provider rotation never ran, the diagnostic said nothing useful, and Katana stayed unmeasured for that run without any notice.

Changes

  • Validated parsing (parse_block_timestamp): RPC error (code and message), null result, a non-dict body and a missing or non-hex timestamp each raise BlockTimestampUnavailableError with a specific reason.
  • Bounded retry and fallback: response-level failures are retried up to 3 times, 5s apart, moving to the next configured provider between attempts (new Web3Client.rotate_provider(), which does nothing when only one provider is set). Transport failures are not retried again because make_request has already gone through every provider with backoff.
  • Credential-safe diagnostics: provider URLs usually carry the API key. The new utils.formatting.redact_urls reduces URLs to scheme and host and strips the scheme-less url: /path in urllib3 errors. The shared provider logs and ProviderConnectionError in utils/web3_wrapper.py now use it, and invalid provider URLs are no longer logged. Transport-failure alerts name only the exception type, so exception text never reaches Telegram.
  • Unknown lag counts as degraded coverage, separate from fresh and stale:
    • A single unmeasurable run is only logged. The observed incident cleared on the next run, so it would not alert.
    • After --unknown-alert-after-runs consecutive unmeasurable runs (default 2, env INDEXER_UNKNOWN_ALERT_AFTER_RUNS), a silent Envio-channel alert goes out with the streak, the last indexed block and the reason. It uses the existing per-chain cooldown.
    • When the chain can be measured again, a one-time "lag measurable again" note goes out and the state resets.
    • The stale alert state is left alone. A stale chain that becomes unmeasurable gets no "caught up" note.
  • The other chains are always checked, and a missing RPC client for one chain no longer stops the rest.
  • README and monitoring.yaml are updated.

Testing

  • New tests cover every malformed or error response shape, a null result followed by success on a retry, running out of attempts, URL redaction on transport failure, a missing client, a one-off unknown run (no alert), unknown → alert → cooldown → recovery, and an unmeasurable stale chain (no false recovery).
  • uv run pytest: 2117 passed, 4 skipped. ruff check, ruff format --check and ty check are clean.
  • Ran the script against the live indexer with an isolated cache: all 5 expected chains were measured and fresh. I also confirmed that Katana RPC returns {"result": null} for a block it does not have.

🤖 Generated with Claude Code

spalen0 and others added 3 commits November 8, 2026 17:55
A null/error/malformed eth_getBlockByNumber response surfaced as a generic
"'NoneType' object is not subscriptable" warning and left the chain silently
unmeasured. Now:

- Validate the raw JSON-RPC response and raise BlockTimestampUnavailableError
  with a specific, URL-redacted diagnostic.
- Retry response-level failures up to 3 times, rotating providers between
  attempts (transport failures already rotate inside make_request).
- Track unknown lag per chain as degraded coverage: alert after 2 consecutive
  unmeasurable runs (--unknown-alert-after-runs), respect the cooldown, and
  send a "measurable again" note on recovery. Stale state is left untouched.
- Add utils.formatting.redact_urls and Web3Client.rotate_provider.

Closes #465

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop tests and an assertion whose behaviour is already covered elsewhere.
Coverage of check_indexer_freshness.py is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Provider rotation logged full URLs, including the API key, and urllib3
connection errors repeat the key in a scheme-less "url: /v2/<key>" path that
redact_urls did not recognise, so persistent failures forwarded it to Telegram.

- Redact URLs in the shared provider logs and in ProviderConnectionError.
- Stop logging invalid provider URLs, which cannot be recognised for redaction.
- Teach redact_urls the urllib3 request-path form.
- Report only the exception type in transport-failure alerts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spalen0
spalen0 merged commit a41b8ea into main Nov 8, 2026
3 checks passed
@spalen0
spalen0 deleted the iss-465 branch November 8, 2026 20:05
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.

Indexer freshness: handle unavailable Katana block timestamps explicitly

1 participant