Skip to content

apollo_dashboard: alert on the Chainlink oracle guard trips - #14948

Open
asaf-sw wants to merge 1 commit into
asaf/l1-oracle-a5-guard-countersfrom
asaf/l1-oracle-08-oracle-staleness-alert
Open

apollo_dashboard: alert on the Chainlink oracle guard trips#14948
asaf-sw wants to merge 1 commit into
asaf/l1-oracle-a5-guard-countersfrom
asaf/l1-oracle-08-oracle-staleness-alert

Conversation

@asaf-sw

@asaf-sw asaf-sw commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Surfaces two of the Chainlink client's guards to on-call, on top of the config
switch that makes the client selectable.

  • P4 when a feed reading is rejected for falling outside the freshness window.
    This is Ohad's "alert if price isn't fresh from the last day": the feeds
    guarantee an update every 24h, so a rejection means the feed stopped publishing
    or its updated_at is implausible. The counter covers both directions, since
    a timestamp too far in the future is the poisoning case the guard exists to
    reject, an unbounded updated_at otherwise reading as permanently fresh.
  • P3 when a rate is rejected for falling outside the configured sanity bounds.
    Higher, because this is the wrong-feed and poisoned-feed detector, and the one
    guard consensus cannot substitute for: validators check only that they agree
    with each other, and every node reads the same chain state, so a plausible but
    wrong price produces unanimous agreement.

The other two guards get no alert. Every guard trip also increments the pair's
error_count, which already carries a paging alert, so invalid-answer and
contract-call trips would page twice for a case that alert covers. These two earn
their own because they name a cause the aggregate cannot.

or vector(0) keeps each query defined before its counter first exists, since
sum over an empty vector returns empty rather than zero and the generated rules
page on their no-data state. That is also why these apply to observers where the
sibling oracle alerts do not: NotApplicable wraps the whole expression in an
is_observer gate that discards the fallback wherever that metric is absent.
Observers read the same feeds, so a trip is environment-wide either way.

The counters stay flat while a feed is served over HTTP, so these alerts are
silent until a feed is switched to Chainlink.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

asaf-sw commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Comment thread crates/apollo_dashboard/src/alert_scenarios/chainlink_oracle.rs
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from 666c587 to fb8da64 Compare August 12, 2026 06:36
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch 2 times, most recently from 0455769 to 263e2f4 Compare August 12, 2026 17:52
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from fb8da64 to 3959ddb Compare August 12, 2026 17:52
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from 3959ddb to c5be2f0 Compare August 13, 2026 10:56
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 263e2f4 to 81c5c6c Compare August 13, 2026 10:56
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from c5be2f0 to 18d139e Compare August 13, 2026 12:00
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 81c5c6c to 421b4d4 Compare August 13, 2026 12:00
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from 18d139e to e731706 Compare August 13, 2026 14:49
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 421b4d4 to 4592d64 Compare August 13, 2026 14:49

@matanl-starkware matanl-starkware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matanl-starkware reviewed 5 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on asaf-sw).


crates/apollo_dashboard/src/alert_scenarios/chainlink_oracle.rs line 44 at r2 (raw file):

        "Chainlink oracle feed outside the freshness window",
        &CHAINLINK_ORACLE_STALE_FEED_COUNT,
        AlertSeverity::WorkingHours,

The severity should be defined per env (by replacer)

Code quote:

AlertSeverity::WorkingHours,

@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 4592d64 to 0ac9247 Compare August 16, 2026 11:31
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from e731706 to a3c6dff Compare August 16, 2026 11:31
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 0ac9247 to e00c341 Compare August 16, 2026 13:12
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from a3c6dff to 38afebd Compare August 16, 2026 13:12

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e00c341. Configure here.

Comment thread crates/apollo_dashboard/src/alert_scenarios/chainlink_oracle.rs
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from e00c341 to 3d1b2df Compare August 16, 2026 15:43
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch 2 times, most recently from 3f29042 to 44fc36a Compare August 16, 2026 15:57
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 3d1b2df to b2f16f6 Compare August 16, 2026 15:58
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch 2 times, most recently from ce424ff to 8c0f75d Compare August 17, 2026 06:06
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from c42e211 to 695ba3e Compare August 17, 2026 06:06
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from 8c0f75d to bed0d19 Compare August 17, 2026 06:14
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 695ba3e to 0c9e86b Compare August 17, 2026 06:14
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from bed0d19 to 59d62fe Compare August 17, 2026 06:22
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 0c9e86b to 3699473 Compare August 17, 2026 06:22
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from 59d62fe to b104573 Compare August 17, 2026 09:45
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 3699473 to eeac62e Compare August 17, 2026 09:45
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from b104573 to d45e396 Compare August 17, 2026 11:17
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from eeac62e to 08bbb1d Compare August 17, 2026 11:17
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from d45e396 to 851f7c7 Compare August 17, 2026 11:50
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from 08bbb1d to f315ddb Compare August 17, 2026 11:51
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-05-select-oracle-source branch from 851f7c7 to ccdea0c Compare August 17, 2026 13:52
@asaf-sw
asaf-sw force-pushed the asaf/l1-oracle-08-oracle-staleness-alert branch from f315ddb to 4f379db Compare August 17, 2026 13:52
Surfaces three of the Chainlink client's guards to on-call, on top of the config
switch that makes the client selectable.

- P4 when a feed reading is rejected for being older than the staleness bound.
  This is Ohad's "alert if price isn't fresh from the last day": the feeds
  guarantee an update every 24h, so a rejection means the feed stopped publishing
  or its `updated_at` is implausible.
- P4 when a feed reading is rejected for being dated ahead of the block being
  priced. Same class of failure, hence the same severity. It gets its own alert
  because the client counts the two directions separately, and this is the
  poisoning case the guard exists to reject: an unbounded `updated_at` otherwise
  reads as permanently fresh.
- P3 when a rate is rejected for falling outside the configured sanity bounds.
  Higher, because this is the wrong-feed and poisoned-feed detector, and the one
  guard consensus cannot substitute for: validators check only that they agree
  with each other, and every node reads the same chain state, so a plausible but
  wrong price produces unanimous agreement.

The other two guards get no alert. Every guard trip also increments the pair's
`error_count`, which already carries a paging alert, so invalid-answer and
contract-call trips would page twice for a case that alert covers. These three
earn their own because they name a cause the aggregate cannot.

Every query groups by the guard counters' `currency_pair` label, so the page names
the rejected reading. The ETH/STRK rate reads both USD feeds, so an aggregate over
the label would say a feed went stale without saying which. The `or vector(0)`
fallback carries no `currency_pair` label, so once a counter exists it joins the
per-pair instances as an unlabeled one holding at zero.

`or vector(0)` keeps each query defined before its counter first exists, since
`sum` over an empty vector returns empty rather than zero and the generated rules
page on their no-data state. That is also why these apply to observers where the
sibling oracle alerts do not: `NotApplicable` wraps the whole expression in an
`is_observer` gate that discards the fallback wherever that metric is absent.
Observers read the same feeds, so a trip is environment-wide either way.

The counters stay flat while a feed is served over HTTP, so these alerts are
silent until a feed is switched to Chainlink.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@matanl-starkware matanl-starkware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matanl-starkware made 2 comments.
Reviewable status: 1 of 5 files reviewed, 3 unresolved discussions (waiting on asaf-sw).


crates/apollo_dashboard/src/alert_scenarios/chainlink_oracle.rs line 1 at r4 (raw file):

use apollo_l1_gas_price::metrics::{

I'm not sure we need a different file for chainlink alerts.
Let's try to consolidate all Oracle-related alerts into a single file, preferably the same alerts.

Code quote:

use apollo_l1_gas_price::metrics::{

crates/apollo_dashboard/src/alert_scenarios/chainlink_oracle.rs line 37 at r4 (raw file):

/// bound. The feeds guarantee an update every 24h, so a rejection means the feed stopped publishing
/// or its `updated_at` is implausible.
pub(crate) fn get_chainlink_oracle_stale_feed_alert() -> Alert {

I should re-review this PR after the metrics are combined (all errors together)

Code quote:

get_chainlink_oracle_stale_feed_alert

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.

3 participants