Skip to content

Commit 00256de

Browse files
apollo_consensus_orchestrator: wire SNIP-35 fee_proposal into build_proposal
1 parent fe3bed1 commit 00256de

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: GasPrice::default(),
183+
fee_proposal: 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
@@ -70,7 +70,7 @@ use tokio::task::JoinHandle;
7070
use tokio::time::sleep;
7171
use tokio_util::sync::CancellationToken;
7272
use tokio_util::task::AbortOnDropHandle;
73-
use tracing::{error, error_span, info, instrument, trace, warn, Instrument};
73+
use tracing::{debug, error, error_span, info, instrument, trace, warn, Instrument};
7474

7575
use crate::build_proposal::{build_proposal, BuildProposalError, ProposalBuildArguments};
7676
use crate::cende::{
@@ -89,8 +89,21 @@ use crate::metrics::{
8989
record_validate_proposal_failure,
9090
register_metrics,
9191
CONSENSUS_L2_GAS_PRICE,
92+
SNIP35_FEE_ACTUAL,
93+
SNIP35_FEE_PROPOSAL,
94+
SNIP35_FEE_TARGET,
95+
SNIP35_STRK_USD_RATE,
96+
};
97+
use crate::snip35::{
98+
compute_fee_actual,
99+
compute_fee_proposal,
100+
compute_fee_target,
101+
FEE_PROPOSAL_MARGIN_PPT,
102+
FEE_PROPOSAL_WINDOW_SIZE,
103+
ORACLE_L2_GAS_FLOOR_MAX_FRI,
104+
ORACLE_L2_GAS_FLOOR_MIN_FRI,
105+
TARGET_ATTO_USD_PER_L2_GAS,
92106
};
93-
use crate::snip35::FEE_PROPOSAL_WINDOW_SIZE;
94107
use crate::utils::{
95108
convert_to_sn_api_block_info,
96109
make_gas_price_params,
@@ -352,6 +365,7 @@ impl SequencerConsensusContext {
352365
sequencer,
353366
timestamp: BlockTimestamp(init.timestamp),
354367
l1_da_mode: init.l1_da_mode,
368+
fee_proposal: init.fee_proposal,
355369
// TODO(guy.f): Figure out where/if to get the values below from and fill them.
356370
..Default::default()
357371
};
@@ -370,16 +384,64 @@ impl SequencerConsensusContext {
370384
/// Returns the next L2 gas price without mutating context. Used when building the fin and when
371385
/// updating at decision time.
372386
fn calculate_next_l2_gas_price(&self, height: BlockNumber, l2_gas_used: GasAmount) -> GasPrice {
387+
let fee_actual = self.compute_fee_actual();
373388
calculate_next_l2_gas_price_for_fin(
374389
self.l2_gas_price,
375390
height,
376391
l2_gas_used,
377392
self.config.dynamic_config.override_l2_gas_price_fri,
378393
&self.config.dynamic_config.min_l2_gas_price_per_height,
379-
None,
394+
fee_actual,
380395
)
381396
}
382397

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

648713
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
@@ -24,6 +24,18 @@ pub(crate) const PPT_DENOMINATOR: u128 = 1000;
2424
/// Number of fee_proposal values used to compute fee_actual (SNIP-35).
2525
pub(crate) const FEE_PROPOSAL_WINDOW_SIZE: usize = 10;
2626

27+
/// Maximum fee_proposal change per block in parts per thousand (SNIP-35: 0.2%).
28+
pub(crate) const FEE_PROPOSAL_MARGIN_PPT: u128 = 2;
29+
30+
/// Target USD cost per L2 gas unit in atto-USD ($3e-9 = 3_000_000_000 atto-USD).
31+
pub(crate) const TARGET_ATTO_USD_PER_L2_GAS: u128 = 3_000_000_000;
32+
33+
/// Hard minimum for the oracle-derived floor (FRI).
34+
pub(crate) const ORACLE_L2_GAS_FLOOR_MIN_FRI: u128 = 8_000_000_000; // 8 gwei, matches MIN_ALLOWED_GAS_PRICE
35+
36+
/// Hard maximum for the oracle-derived floor (FRI).
37+
pub(crate) const ORACLE_L2_GAS_FLOOR_MAX_FRI: u128 = u128::MAX;
38+
2739
/// Compute fee_actual from the last `window_size` fee_proposal values (SNIP-35).
2840
/// Returns the average of the two middle values, rounded down.
2941
/// Returns `None` if fewer than `window_size` proposals are available.

crates/apollo_consensus_orchestrator/src/test_utils.rs

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

0 commit comments

Comments
 (0)