apollo_consensus_orchestrator,apollo_l1_gas_price: bound the per-block eth to fri rate change - #14973
Open
asaf-sw wants to merge 1 commit into
Open
Conversation
This was referenced Aug 16, 2026
Open
Contributor
Author
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 05:32
f124225 to
c42e211
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 05:32
e435c9a to
097cc5d
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 06:06
c42e211 to
695ba3e
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 06:06
097cc5d to
af148bd
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 06:14
695ba3e to
0c9e86b
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 06:15
af148bd to
d4a39ef
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 06:22
0c9e86b to
3699473
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 06:23
d4a39ef to
dc5ac9b
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 09:45
3699473 to
eeac62e
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 09:45
dc5ac9b to
8875f7e
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 11:17
eeac62e to
08bbb1d
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 11:18
8875f7e to
543b60b
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 11:51
08bbb1d to
f315ddb
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 11:51
543b60b to
b045861
Compare
…k eth to fri rate change The Chainlink oracle guards each reading with absolute sanity bounds, but those are deliberately wide (the STRK/USD band spans five decades) because their job is wrong-feed detection. A manipulated but plausible feed value passes them. A bound relative to the previously accepted rate is what catches that, and #14944 names it as a precondition before any feed is switched to the on-chain oracle. The bound lives in the orchestrator, not in the Chainlink client. A bound anchored to the client's own last read is node-local history: two validators with different read histories would accept and reject different proposals. Anchoring instead on the rate implied by the previous block's recorded wei and fri prices (calculate_eth_to_fri_rate) makes the bound a function of the block header and of config alone, so every validator derives the same band and maps a given oracle reading to the same rate. It also covers both oracle sources, HTTP and Chainlink, which a bound inside the Chainlink client would not. A fresh rate outside the band is clamped to the band's edge rather than rejected, following the shape of the per-block L2 gas price clamp: clamping bounds per-block damage and forces a manipulation to be sustained across many blocks to move the price far. max_eth_to_fri_rate_change_ppt defaults to 50 (5% per block). The ETH and STRK feeds update on deviation thresholds of roughly 0.5% and 1%, so the ratio steps by at most ~1.5% between consecutive blocks and a normal feed update never trips the bound. On the other side, one manipulated reading moves the rate by at most 5%, and doubling it requires holding the feed for about 15 consecutive blocks (~40 seconds at mainnet block times), long enough for the oracle staleness and guard alerts to fire. A genuine sharp move is tracked within a few blocks. When there is no previous block (startup, or the path that falls back to the minimal config values) there is no anchor, so the oracle rate is used as is. That path is explicit. Clamping increments consensus_eth_to_fri_rate_clamped and logs a warning.
asaf-sw
force-pushed
the
asaf/l1-oracle-08-oracle-staleness-alert
branch
from
August 17, 2026 13:52
f315ddb to
4f379db
Compare
asaf-sw
force-pushed
the
asaf/l1-oracle-10-rate-change-bound
branch
from
August 17, 2026 13:52
b045861 to
a4a9809
Compare
asaf-sw
marked this pull request as ready for review
August 17, 2026 14:05
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.

Bounds how far the ETH/FRI exchange rate may move from one block to the next, and clamps a fresh oracle rate into that band.
Why
The Chainlink oracle (#14942) guards each reading with absolute sanity bounds, but those are deliberately wide: the STRK/USD band spans five decades, because its job is wrong-feed detection. A manipulated but plausible feed value passes them. Only a bound relative to the previously accepted rate catches that, and #14944 names this bound as a precondition before any feed is flipped to the on-chain oracle.
Why the orchestrator and not the oracle client
A bound anchored to the Chainlink client's own last read is node-local history: two validators with different read histories would accept and reject different proposals, which is exactly what the original TODO ruled out.
The bound is implemented in
apollo_consensus_orchestrator::utils, on the oracle-fetch path, where the previous block is already in hand. The anchor is the rate implied by the previous block's recorded wei and fri prices, derived with the existingcalculate_eth_to_fri_rate. That makes the bound a function of the block header and of config alone: every validator derives the same band and maps a given oracle reading to the same rate. It also covers both oracle sources, HTTP and Chainlink, which a bound inside the Chainlink client would not.The TODO in
chainlink_oracle.rsis replaced with a comment pointing at where the relative bound now lives.Clamp, not reject
A rate outside the band is clamped to the band's edge, following the shape of the existing per-block L2 gas price clamp (
calculate_next_l2_gas_price_for_fin), including multiply-before-divide inU256and a truncating division that makes the band marginally tighter, never looser. Clamping bounds per-block damage and forces a manipulation to be sustained across many blocks to move the price far.Default
max_eth_to_fri_rate_change_pptdefaults to50, i.e. 5% per block.No previous block
At startup, and on the path that falls back to the minimal config values, there is no anchor, so the oracle rate is used as is. That branch is explicit and commented rather than implicit.
Observability
Clamping increments
consensus_eth_to_fri_rate_clamped(registered alongside its neighbours) and logs a warning naming the oracle rate, the previous block's implied rate, and the clamped result.Config wiring
max_eth_to_fri_rate_change_pptis onContextDynamicConfig, and is present in the node config schema,consensus_manager_config.json, andreplacer_consensus_manager_config.json(a param in the schema but missing from the app configs crash-loops every node).Tests
utils_test.rs: rate inside the bound passes through unchanged; rate above the bound clamps to the ceiling; rate below clamps to the floor; boundary exactly at each edge is unchanged; no previous block means no clamping; the metric increments only when clamping; and two independently constructed instances, given the same previous block and the same oracle rate, produce the same result even when they have made a different number of oracle calls.🤖 Generated with Claude Code