Skip to content

Commit 844a7fa

Browse files
apollo_consensus_orchestrator: wire SNIP-35 fee_proposal into build_proposal
1 parent 3d326ec commit 844a7fa

4 files changed

Lines changed: 88 additions & 5 deletions

File tree

crates/apollo_consensus_orchestrator/src/build_proposal.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ pub(crate) struct ProposalBuildArguments {
7676
pub override_l2_gas_price_fri: Option<u128>,
7777
pub min_l2_gas_price_per_height: Vec<PricePerHeight>,
7878
pub compare_retrospective_block_hash: bool,
79+
/// SNIP-35: proposer's fee_proposal for this block.
80+
pub fee_proposal: GasPrice,
81+
/// SNIP-35: current fee_actual from the sliding window.
82+
pub fee_actual: Option<GasPrice>,
7983
}
8084

8185
type BuildProposalResult<T> = Result<T, BuildProposalError>;
@@ -176,7 +180,7 @@ async fn initiate_build(args: &mut ProposalBuildArguments) -> BuildProposalResul
176180
starknet_version: starknet_api::block::StarknetVersion::LATEST,
177181
// TODO(Asmaa): Put the real value once we have it.
178182
version_constant_commitment: Default::default(),
179-
fee_proposal_fri: None,
183+
fee_proposal_fri: Some(args.fee_proposal),
180184
};
181185

182186
let retrospective_block_hash = wait_for_retrospective_block_hash(
@@ -319,7 +323,7 @@ async fn get_proposal_content(
319323
info.l2_gas_used,
320324
args.override_l2_gas_price_fri,
321325
&args.min_l2_gas_price_per_height,
322-
None,
326+
args.fee_actual,
323327
);
324328
let fin_payload = ProposalFinPayload {
325329
commitment_parts: CommitmentParts::from(&info),

crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ use tokio::task::JoinHandle;
7171
use tokio::time::sleep;
7272
use tokio_util::sync::CancellationToken;
7373
use tokio_util::task::AbortOnDropHandle;
74-
use tracing::{error, error_span, info, instrument, trace, warn, Instrument};
74+
use tracing::{debug, error, error_span, info, instrument, trace, warn, Instrument};
7575

7676
use crate::build_proposal::{build_proposal, BuildProposalError, ProposalBuildArguments};
7777
use crate::cende::{
@@ -90,8 +90,21 @@ use crate::metrics::{
9090
record_validate_proposal_failure,
9191
register_metrics,
9292
CONSENSUS_L2_GAS_PRICE,
93+
SNIP35_FEE_ACTUAL,
94+
SNIP35_FEE_PROPOSAL,
95+
SNIP35_FEE_TARGET,
96+
SNIP35_STRK_USD_RATE,
97+
};
98+
use crate::snip35::{
99+
compute_fee_actual,
100+
compute_fee_proposal,
101+
compute_fee_target,
102+
FEE_PROPOSAL_MARGIN_PPT,
103+
FEE_PROPOSAL_WINDOW_SIZE,
104+
ORACLE_L2_GAS_FLOOR_MAX_FRI,
105+
ORACLE_L2_GAS_FLOOR_MIN_FRI,
106+
TARGET_ATTO_USD_PER_L2_GAS,
93107
};
94-
use crate::snip35::FEE_PROPOSAL_WINDOW_SIZE;
95108
use crate::utils::{
96109
convert_to_sn_api_block_info,
97110
make_gas_price_params,
@@ -353,6 +366,7 @@ impl SequencerConsensusContext {
353366
sequencer,
354367
timestamp: BlockTimestamp(init.timestamp),
355368
l1_da_mode: init.l1_da_mode,
369+
fee_proposal_fri: init.fee_proposal_fri,
356370
// TODO(guy.f): Figure out where/if to get the values below from and fill them.
357371
..Default::default()
358372
};
@@ -371,16 +385,64 @@ impl SequencerConsensusContext {
371385
/// Returns the next L2 gas price without mutating context. Used when building the fin and when
372386
/// updating at decision time.
373387
fn calculate_next_l2_gas_price(&self, height: BlockNumber, l2_gas_used: GasAmount) -> GasPrice {
388+
let fee_actual = self.compute_fee_actual();
374389
calculate_next_l2_gas_price_for_fin(
375390
self.l2_gas_price,
376391
height,
377392
l2_gas_used,
378393
self.config.dynamic_config.override_l2_gas_price_fri,
379394
&self.config.dynamic_config.min_l2_gas_price_per_height,
380-
None,
395+
fee_actual,
381396
)
382397
}
383398

399+
/// SNIP-35: compute fee_actual from the sliding window of fee_proposals.
400+
fn compute_fee_actual(&self) -> Option<GasPrice> {
401+
let proposals: Vec<GasPrice> = self.fee_proposals_window.iter().copied().collect();
402+
compute_fee_actual(&proposals, FEE_PROPOSAL_WINDOW_SIZE)
403+
}
404+
405+
/// SNIP-35: compute the fee_proposal this node should publish.
406+
async fn compute_snip35_fee_proposal(&self, timestamp: u64) -> GasPrice {
407+
// compute_fee_actual returns None for zero median or insufficient data, falling back to
408+
// the current l2_gas_price. This ensures proposer and validator use the same fallback.
409+
let fee_actual = self.compute_fee_actual().unwrap_or(self.l2_gas_price);
410+
SNIP35_FEE_ACTUAL.set_lossy(fee_actual.0);
411+
412+
// The oracle reuses ExchangeRateOracleClientTrait configured with a STRK/USD endpoint.
413+
let fee_target = match &self.deps.strk_exchange_rate_oracle {
414+
Some(oracle) => match oracle.eth_to_fri_rate(timestamp).await {
415+
Ok(rate) if rate > 0 => {
416+
SNIP35_STRK_USD_RATE.set_lossy(rate);
417+
let target = compute_fee_target(
418+
TARGET_ATTO_USD_PER_L2_GAS,
419+
rate,
420+
ORACLE_L2_GAS_FLOOR_MIN_FRI,
421+
ORACLE_L2_GAS_FLOOR_MAX_FRI,
422+
);
423+
SNIP35_FEE_TARGET.set_lossy(target.0);
424+
Some(target)
425+
}
426+
Ok(_) => {
427+
warn!("STRK/USD oracle returned zero rate, freezing fee_proposal");
428+
None
429+
}
430+
Err(e) => {
431+
warn!("STRK/USD oracle error: {e:?}, freezing fee_proposal");
432+
None
433+
}
434+
},
435+
None => {
436+
debug!("No STRK/USD oracle configured, freezing fee_proposal");
437+
None
438+
}
439+
};
440+
441+
let proposal = compute_fee_proposal(fee_target, fee_actual, FEE_PROPOSAL_MARGIN_PPT);
442+
SNIP35_FEE_PROPOSAL.set_lossy(proposal.0);
443+
proposal
444+
}
445+
384446
fn update_l2_gas_price(&mut self, height: BlockNumber, l2_gas_used: GasAmount) {
385447
self.l2_gas_price = self.calculate_next_l2_gas_price(height, l2_gas_used);
386448
let gas_price_u64 = u64::try_from(self.l2_gas_price.0).unwrap_or(u64::MAX);
@@ -613,6 +675,7 @@ impl ConsensusContext for SequencerConsensusContext {
613675
BehaviorMode::Echonet => true,
614676
BehaviorMode::Starknet => false,
615677
};
678+
let fee_proposal = self.compute_snip35_fee_proposal(self.deps.clock.unix_now()).await;
616679
let round = build_param.round;
617680
let args = ProposalBuildArguments {
618681
deps: self.deps.clone(),
@@ -646,6 +709,8 @@ impl ConsensusContext for SequencerConsensusContext {
646709
.config
647710
.dynamic_config
648711
.compare_retrospective_block_hash,
712+
fee_proposal,
713+
fee_actual: self.compute_fee_actual(),
649714
};
650715

651716
let handle = tokio::spawn(

crates/apollo_consensus_orchestrator/src/snip35/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ pub(crate) const PPT_DENOMINATOR: u128 = 1000;
2727
/// Number of fee_proposal values used to compute fee_actual (SNIP-35).
2828
pub(crate) const FEE_PROPOSAL_WINDOW_SIZE: usize = 10;
2929

30+
/// Maximum fee_proposal change per block in parts per thousand (SNIP-35: 0.2%).
31+
pub(crate) const FEE_PROPOSAL_MARGIN_PPT: u128 = 2;
32+
33+
/// Target USD cost per L2 gas unit in atto-USD ($3e-9 = 3_000_000_000 atto-USD).
34+
pub(crate) const TARGET_ATTO_USD_PER_L2_GAS: u128 = 3_000_000_000;
35+
36+
/// Hard minimum for the oracle-derived floor (FRI).
37+
pub(crate) const ORACLE_L2_GAS_FLOOR_MIN_FRI: u128 = 8_000_000_000; // 8 gwei, matches MIN_ALLOWED_GAS_PRICE
38+
39+
/// Hard maximum for the oracle-derived floor (FRI).
40+
pub(crate) const ORACLE_L2_GAS_FLOOR_MAX_FRI: u128 = u128::MAX;
41+
3042
/// Compute fee_actual from the last `window_size` `fee_proposal` values (SNIP-35).
3143
///
3244
/// Median rule: for even `window_size`, the average of the two middle values rounded

crates/apollo_consensus_orchestrator/src/test_utils.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ impl From<TestProposalBuildArguments> for ProposalBuildArguments {
510510
override_l2_gas_price_fri: args.override_l2_gas_price_fri,
511511
min_l2_gas_price_per_height: args.min_l2_gas_price_per_height,
512512
compare_retrospective_block_hash: args.compare_retrospective_block_hash,
513+
fee_proposal: GasPrice::default(),
514+
fee_actual: None,
513515
}
514516
}
515517
}

0 commit comments

Comments
 (0)