@@ -68,7 +68,7 @@ use starknet_api::versioned_constants_logic::VersionedConstantsTrait;
6868use tokio:: task:: JoinHandle ;
6969use tokio_util:: sync:: CancellationToken ;
7070use tokio_util:: task:: AbortOnDropHandle ;
71- use tracing:: { error, error_span, info, instrument, trace, warn, Instrument } ;
71+ use tracing:: { debug , error, error_span, info, instrument, trace, warn, Instrument } ;
7272
7373use crate :: build_proposal:: { build_proposal, BuildProposalError , ProposalBuildArguments } ;
7474use crate :: cende:: {
@@ -79,14 +79,25 @@ use crate::cende::{
7979} ;
8080use crate :: fee_market:: {
8181 calculate_next_l2_gas_price_for_fin,
82+ compute_fee_actual,
83+ compute_fee_proposal,
84+ compute_fee_target,
8285 FeeMarketInfo ,
86+ FEE_PROPOSAL_MARGIN_PPT ,
8387 FEE_PROPOSAL_WINDOW_SIZE ,
88+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
89+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
90+ TARGET_ATTO_USD_PER_L2_GAS ,
8491} ;
8592use crate :: metrics:: {
8693 record_build_proposal_failure,
8794 record_validate_proposal_failure,
8895 register_metrics,
8996 CONSENSUS_L2_GAS_PRICE ,
97+ SNIP35_FEE_ACTUAL ,
98+ SNIP35_FEE_PROPOSAL ,
99+ SNIP35_FEE_TARGET ,
100+ SNIP35_STRK_USD_RATE ,
90101} ;
91102use crate :: orchestrator_versioned_constants:: VersionedConstants ;
92103use crate :: utils:: {
@@ -347,6 +358,7 @@ impl SequencerConsensusContext {
347358 sequencer,
348359 timestamp : BlockTimestamp ( init. timestamp ) ,
349360 l1_da_mode : init. l1_da_mode ,
361+ fee_proposal : init. fee_proposal ,
350362 // TODO(guy.f): Figure out where/if to get the values below from and fill them.
351363 ..Default :: default ( )
352364 } ;
@@ -365,16 +377,64 @@ impl SequencerConsensusContext {
365377 /// Returns the next L2 gas price without mutating context. Used when building the fin and when
366378 /// updating at decision time.
367379 fn calculate_next_l2_gas_price ( & self , height : BlockNumber , l2_gas_used : GasAmount ) -> GasPrice {
380+ let fee_actual = self . compute_fee_actual ( ) ;
368381 calculate_next_l2_gas_price_for_fin (
369382 self . l2_gas_price ,
370383 height,
371384 l2_gas_used,
372385 self . config . dynamic_config . override_l2_gas_price_fri ,
373386 & self . config . dynamic_config . min_l2_gas_price_per_height ,
374- None ,
387+ fee_actual ,
375388 )
376389 }
377390
391+ /// SNIP-35: compute fee_actual from the sliding window of fee_proposals.
392+ fn compute_fee_actual ( & self ) -> Option < GasPrice > {
393+ let proposals: Vec < GasPrice > = self . fee_proposals_window . iter ( ) . copied ( ) . collect ( ) ;
394+ compute_fee_actual ( & proposals, FEE_PROPOSAL_WINDOW_SIZE )
395+ }
396+
397+ /// SNIP-35: compute the fee_proposal this node should publish.
398+ async fn compute_snip35_fee_proposal ( & self , timestamp : u64 ) -> GasPrice {
399+ // compute_fee_actual returns None for zero median or insufficient data, falling back to
400+ // the current l2_gas_price. This ensures proposer and validator use the same fallback.
401+ let fee_actual = self . compute_fee_actual ( ) . unwrap_or ( self . l2_gas_price ) ;
402+ SNIP35_FEE_ACTUAL . set_lossy ( fee_actual. 0 ) ;
403+
404+ // The oracle reuses PriceOracleClientTrait configured with a STRK/USD endpoint.
405+ let fee_target = match & self . deps . strk_price_oracle {
406+ Some ( oracle) => match oracle. eth_to_fri_rate ( timestamp) . await {
407+ Ok ( rate) if rate > 0 => {
408+ SNIP35_STRK_USD_RATE . set_lossy ( rate) ;
409+ let target = compute_fee_target (
410+ TARGET_ATTO_USD_PER_L2_GAS ,
411+ rate,
412+ ORACLE_L2_GAS_FLOOR_MIN_FRI ,
413+ ORACLE_L2_GAS_FLOOR_MAX_FRI ,
414+ ) ;
415+ SNIP35_FEE_TARGET . set_lossy ( target. 0 ) ;
416+ Some ( target)
417+ }
418+ Ok ( _) => {
419+ warn ! ( "STRK/USD oracle returned zero rate, freezing fee_proposal" ) ;
420+ None
421+ }
422+ Err ( e) => {
423+ warn ! ( "STRK/USD oracle error: {e:?}, freezing fee_proposal" ) ;
424+ None
425+ }
426+ } ,
427+ None => {
428+ debug ! ( "No STRK/USD oracle configured, freezing fee_proposal" ) ;
429+ None
430+ }
431+ } ;
432+
433+ let proposal = compute_fee_proposal ( fee_target, fee_actual, FEE_PROPOSAL_MARGIN_PPT ) ;
434+ SNIP35_FEE_PROPOSAL . set_lossy ( proposal. 0 ) ;
435+ proposal
436+ }
437+
378438 fn update_l2_gas_price ( & mut self , height : BlockNumber , l2_gas_used : GasAmount ) {
379439 self . l2_gas_price = self . calculate_next_l2_gas_price ( height, l2_gas_used) ;
380440 let gas_price_u64 = u64:: try_from ( self . l2_gas_price . 0 ) . unwrap_or ( u64:: MAX ) ;
@@ -573,6 +633,7 @@ impl ConsensusContext for SequencerConsensusContext {
573633 BehaviorMode :: Echonet => true ,
574634 BehaviorMode :: Starknet => false ,
575635 } ;
636+ let fee_proposal = self . compute_snip35_fee_proposal ( self . deps . clock . unix_now ( ) ) . await ;
576637 let round = build_param. round ;
577638 let args = ProposalBuildArguments {
578639 deps : self . deps . clone ( ) ,
@@ -606,6 +667,8 @@ impl ConsensusContext for SequencerConsensusContext {
606667 . config
607668 . dynamic_config
608669 . compare_retrospective_block_hash ,
670+ fee_proposal,
671+ fee_actual : self . compute_fee_actual ( ) ,
609672 } ;
610673
611674 let handle = tokio:: spawn (
0 commit comments