feat(yearn): alert when the Envio indexer falls behind - #325
Merged
Conversation
The monitors that read from the Envio indexer (large flows, timelock alerts, 3jane borrower watch) go quiet rather than loud when it stalls: GraphQL keeps answering, it just stops returning new rows, so a 24h+ outage is indistinguishable from a quiet day. Add an hourly check that reads chain_metadata and resolves the wall-clock timestamp of each chain's latest_processed_block over JSON-RPC, alerting the errors channel when a chain is more than 60 minutes stale or the endpoint itself is unreachable. The RPC round-trip is what makes the check meaningful: envio parks chain_metadata.block_height at the last processed block once a chain looks caught up, so comparing those two fields would report a stalled indexer as zero blocks behind (same trap documented in the indexer's own monitoring dashboard). Each chain alerts on entering staleness, then at most once per cooldown window (default 6h), then once on recovery — a re-sync can run for days, and today Mainnet is ~206d behind and Katana ~6h. Also move the duplicated format_duration helper into utils/formatting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drop Gnosis and Berachain from the freshness check — the indexer covers them but nothing in this repo reads their events, so they were alerting on data we never consume. That removes the public-RPC fallbacks too, so the check now runs entirely through ChainManager on the six Chain enum members. Fetch the block via a raw eth_getBlockByNumber rather than eth.get_block: web3 rejects the 97-byte PoA extraData on Polygon and pre-Bedrock Optimism blocks, and old blocks are exactly what a lagging indexer points at. Add Web3Client.make_request for that, keeping provider rotation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An empty result set is not good news. If a chain drops out of the indexer's config, or returns from a restart with no processed block, it simply stops appearing in chain_metadata — collect_freshness produced no entry, `not stale` was true, and the job logged the indexer as fresh while that chain's monitors sat blind. Add EXPECTED_CHAINS as the authority on what must be present and alert on anything absent from it, covering both a missing row and a row with no processed block. Missing chains share the per-chain cooldown and the same alert message as lagging ones. EXPECTED_CHAINS is spelled out rather than derived from the Chain enum so that adding an enum member for an unrelated protocol doesn't start alerting that the indexer is missing a chain it was never asked to index. Co-Authored-By: Claude Opus 5 (1M context) <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.
Envio was down for 24+ hours this week and none of our monitoring noticed. The monitors that read from the indexer — large flows, timelock alerts, 3jane borrower watch — go quiet rather than loud when it stalls: GraphQL keeps answering, it just stops returning new rows, so an outage is indistinguishable from a quiet day.
This adds an hourly freshness check that makes that silence loud.
How it works
chain_metadataatENVIO_GRAPHQL_URLfor each chain'slatest_processed_block.ChainManagerand compare it to wall-clock time.--max-lag-minutes(default60) stale, when an expected chain reports no sync state at all, or when the endpoint itself is unset/unreachable/empty.Step 2 is what makes the check trustworthy. Envio parks
chain_metadata.block_heightat the last processed block once a chain looks caught up, so comparing those two fields reports a stalled indexer as zero blocks behind — the same trap called out in the indexer's own monitoring dashboard source.Step 3 covers the inverse trap, caught in review: an empty result set is not good news. If a chain drops out of the indexer's config, or comes back from a restart with no processed block, it stops appearing in
chain_metadata— and a check that only looks at what it was given reports every remaining chain fresh while that chain's monitors sit blind.EXPECTED_CHAINSis the authority on what must be present, and anything absent from it alerts.It already caught a live incident
First dry run this morning found Mainnet 206 days behind, mid re-sync. Half an hour later the indexer had been wiped and restarted from scratch — every chain reset to its start block and is now crawling forward:
Every one of those chains reported itself as caught up via
block_height. Worth someone confirming the re-index is intentional.Notes for review
EXPECTED_CHAINSis spelled out, not derived from theChainenum. The enum is the repo's list of supported chains, not a statement about what Envio indexes — deriving from it would start alerting that the indexer is "missing" a chain nobody asked it to index the moment someone adds an enum member for an unrelated protocol. Add a chain toEXPECTED_CHAINSwhen its events start feeding a monitor.--alert-cooldown-hours). Without it, a multi-day re-sync like the one above posts an identical alert every hour for weeks. Each chain alerts on entering trouble, then at most once per window, then once on recovery. Tracked per chain, so one lagging chain never suppresses another's first alert. Endpoint-down alerts bypass the cooldown — that's the acute outage case.eth_getBlockByNumberinstead ofeth.get_block. web3 rejects the 97-byte PoAextraDataon Polygon and pre-Bedrock Optimism blocks, and old blocks are exactly what a lagging indexer points at — this silently blanked those two chains until fixed. AddedWeb3Client.make_requestfor it, so provider rotation and retries still apply.Alerts route to the existing errors channel (
TELEGRAM_*_ERRORS) labelled[yearn]. Scheduled first in the hourly profile so the freshness signal lands before the monitors that depend on it.Also moves the duplicated
format_durationhelper intoutils/formatting.py.Testing
ruff format/ruff checkclean.mypyadds no new errors (repo-wide run fails on a pre-existing stalebuild/duplicate-module error).🤖 Generated with Claude Code