Skip to content

Commit 6bd60bb

Browse files
apollo_consensus_orchestrator: add SNIP-35 fee_proposal validation
1 parent e61ede6 commit 6bd60bb

3 files changed

Lines changed: 29 additions & 0 deletions

File tree

crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ impl ConsensusContext for SequencerConsensusContext {
734734
.override_l2_gas_price_fri
735735
.map(GasPrice)
736736
.unwrap_or(self.l2_gas_price),
737+
fee_actual: self.compute_fee_actual(),
737738
};
738739
self.validate_current_round_proposal(
739740
init,
@@ -988,6 +989,7 @@ impl ConsensusContext for SequencerConsensusContext {
988989
.override_l2_gas_price_fri
989990
.map(GasPrice)
990991
.unwrap_or(self.l2_gas_price),
992+
fee_actual: self.compute_fee_actual(),
991993
};
992994
self.validate_current_round_proposal(
993995
init,

crates/apollo_consensus_orchestrator/src/validate_proposal.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use strum::{EnumDiscriminants, EnumIter, IntoStaticStr, VariantNames};
3535
use tokio_util::sync::CancellationToken;
3636
use tracing::{debug, info, instrument, warn};
3737

38+
use crate::fee_market::{FEE_PROPOSAL_MARGIN_PPT, PPT_DENOMINATOR};
3839
use crate::metrics::{
3940
CONSENSUS_NUM_BATCHES_IN_PROPOSAL,
4041
CONSENSUS_NUM_TXS_IN_PROPOSAL,
@@ -76,6 +77,8 @@ pub(crate) struct ProposalInitValidation {
7677
pub previous_proposal_init: Option<PreviousProposalInitInfo>,
7778
pub l1_da_mode: L1DataAvailabilityMode,
7879
pub l2_gas_price_fri: GasPrice,
80+
/// SNIP-35: fee_actual from the sliding window. None during initiation (<10 blocks).
81+
pub fee_actual: Option<GasPrice>,
7982
}
8083

8184
/// Parameters for deadline and cancellation handling during proposal finalization.
@@ -320,6 +323,29 @@ async fn is_proposal_init_valid(
320323
),
321324
));
322325
}
326+
327+
// SNIP-35: validate fee_proposal is within the configured margin of fee_actual.
328+
// During initiation (fee_actual is None, <window_size blocks), bounds are not enforced.
329+
if let Some(fee_actual) = proposal_init_validation.fee_actual {
330+
let fee_proposal = init_proposed.fee_proposal;
331+
let margin_ppt = FEE_PROPOSAL_MARGIN_PPT;
332+
let lower_bound =
333+
fee_actual.0.saturating_mul(PPT_DENOMINATOR) / (PPT_DENOMINATOR + margin_ppt);
334+
let upper_bound =
335+
fee_actual.0.saturating_mul(PPT_DENOMINATOR + margin_ppt) / PPT_DENOMINATOR;
336+
if fee_proposal.0 < lower_bound || fee_proposal.0 > upper_bound {
337+
return Err(ValidateProposalError::InvalidProposalInit(
338+
init_proposed.clone(),
339+
proposal_init_validation.clone(),
340+
format!(
341+
"Fee proposal out of SNIP-35 bounds: fee_actual={}, fee_proposal={}, allowed \
342+
range=[{lower_bound}, {upper_bound}]",
343+
fee_actual.0, fee_proposal.0
344+
),
345+
));
346+
}
347+
}
348+
323349
Ok(())
324350
}
325351

crates/apollo_consensus_orchestrator/src/validate_proposal_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ fn create_proposal_validate_arguments()
9494
previous_proposal_init: None,
9595
l1_da_mode: L1DataAvailabilityMode::Blob,
9696
l2_gas_price_fri: VersionedConstants::latest_constants().min_gas_price,
97+
fee_actual: None,
9798
};
9899
let proposal_id = ProposalId(1);
99100
let timeout = TIMEOUT;

0 commit comments

Comments
 (0)