Skip to content

Commit c410076

Browse files
apollo_consensus_orchestrator: add SNIP-35 fee_proposal validation
1 parent 00256de commit c410076

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
@@ -773,6 +773,7 @@ impl ConsensusContext for SequencerConsensusContext {
773773
.override_l2_gas_price_fri
774774
.map(GasPrice)
775775
.unwrap_or(self.l2_gas_price),
776+
fee_actual: self.compute_fee_actual(),
776777
};
777778
self.validate_current_round_proposal(
778779
init,
@@ -1064,6 +1065,7 @@ impl ConsensusContext for SequencerConsensusContext {
10641065
.override_l2_gas_price_fri
10651066
.map(GasPrice)
10661067
.unwrap_or(self.l2_gas_price),
1068+
fee_actual: self.compute_fee_actual(),
10671069
};
10681070
self.validate_current_round_proposal(
10691071
init,

crates/apollo_consensus_orchestrator/src/validate_proposal.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use crate::metrics::{
4242
CONSENSUS_PROPOSAL_FIN_MISMATCH,
4343
};
4444
use crate::sequencer_consensus_context::{BuiltProposals, SequencerConsensusContextDeps};
45+
use crate::snip35::{FEE_PROPOSAL_MARGIN_PPT, PPT_DENOMINATOR};
4546
use crate::utils::{
4647
convert_to_sn_api_block_info,
4748
get_l1_prices_in_fri_and_wei,
@@ -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)