Skip to content

Commit b64fa48

Browse files
apollo_consensus_orchestrator: include fee_proposal_fri in proposal commitment hash
1 parent a2dc0e3 commit b64fa48

8 files changed

Lines changed: 118 additions & 46 deletions

File tree

crates/apollo_consensus_orchestrator/src/build_proposal.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use tracing::{debug, info, trace, warn};
4141

4242
use crate::fee_market::calculate_next_l2_gas_price_for_fin;
4343
use crate::sequencer_consensus_context::{BuiltProposals, SequencerConsensusContextDeps};
44+
use crate::snip35::proposal_commitment_from;
4445
use crate::utils::{
4546
convert_to_sn_api_block_info,
4647
get_l1_prices_in_fri_and_wei,
@@ -269,8 +270,10 @@ async fn get_proposal_content(
269270
})?;
270271
}
271272
GetProposalContent::Finished(info) => {
272-
let proposal_commitment =
273-
ProposalCommitment(info.proposal_commitment.partial_block_hash.0);
273+
let proposal_commitment = proposal_commitment_from(
274+
info.proposal_commitment.partial_block_hash,
275+
Some(args.fee_proposal),
276+
);
274277
content = truncate_to_executed_txs(&mut content, info.final_n_executed_txs);
275278

276279
info!(

crates/apollo_consensus_orchestrator/src/build_proposal_test.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ use apollo_batcher_types::batcher_types::{
88
ProposalCommitment,
99
};
1010
use apollo_batcher_types::communication::BatcherClientError;
11-
use apollo_consensus::types::ProposalCommitment as ConsensusProposalCommitment;
1211
use apollo_infra::component_client::ClientError;
1312
use apollo_transaction_converter::{MockTransactionConverterTrait, TransactionConverterError};
1413
use assert_matches::assert_matches;
14+
use starknet_api::block::GasPrice;
1515
use starknet_api::block_hash::block_hash_calculator::BlockHeaderCommitments;
1616
use starknet_api::core::ClassHash;
1717
use starknet_api::execution_resources::GasAmount;
1818
use tokio_util::task::AbortOnDropHandle;
1919

2020
use crate::build_proposal::{build_proposal, BuildProposalError};
21+
use crate::snip35::proposal_commitment_from;
2122
use crate::test_utils::{create_proposal_build_arguments, INTERNAL_TX_BATCH, PARTIAL_BLOCK_HASH};
2223

2324
#[tokio::test]
@@ -44,7 +45,7 @@ async fn build_proposal_succeed() {
4445
tokio::time::sleep(Duration::from_millis(100)).await;
4546

4647
let res = build_proposal(proposal_args.into()).await.unwrap();
47-
assert_eq!(res, ConsensusProposalCommitment(PARTIAL_BLOCK_HASH.0));
48+
assert_eq!(res, proposal_commitment_from(PARTIAL_BLOCK_HASH, Some(GasPrice::default())));
4849
}
4950

5051
#[tokio::test]

crates/apollo_consensus_orchestrator/src/sequencer_consensus_context.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ use crate::snip35::{
9999
compute_fee_actual,
100100
compute_fee_proposal,
101101
compute_fee_target,
102+
proposal_commitment_from,
102103
FEE_PROPOSAL_MARGIN_PPT,
103104
FEE_PROPOSAL_WINDOW_SIZE,
104105
ORACLE_L2_GAS_FLOOR_MAX_FRI,
@@ -181,8 +182,10 @@ impl BuiltProposals {
181182
proposal_id: &ProposalId,
182183
finished_info: FinishedProposalInfo,
183184
) {
184-
let proposal_commitment =
185-
ProposalCommitment(finished_info.proposal_commitment.partial_block_hash.0);
185+
let proposal_commitment = proposal_commitment_from(
186+
finished_info.proposal_commitment.partial_block_hash,
187+
init.fee_proposal_fri,
188+
);
186189

187190
let height = init.height;
188191
let round = init.round;
@@ -549,9 +552,13 @@ impl SequencerConsensusContext {
549552
compiled_class_hashes_for_migration: central_objects
550553
.compiled_class_hashes_for_migration,
551554
proposal_commitment: commitment,
555+
// TODO(SNIP-35): plumb the parent block's `fee_proposal_fri` here once
556+
// `central_objects.parent_proposal_commitment` carries it (or read from
557+
// BlockHeaderWithoutHash storage). Today we pass `None`, which means
558+
// pre-V0_14_3 commitments — correct for non-SNIP-35 deployments only.
552559
parent_proposal_commitment: central_objects
553560
.parent_proposal_commitment
554-
.map(|commitment| ProposalCommitment(commitment.partial_block_hash.0)),
561+
.map(|c| proposal_commitment_from(c.partial_block_hash, None)),
555562
recent_block_hashes: self.collect_recent_block_hashes(height).await,
556563
})
557564
.await

crates/apollo_consensus_orchestrator/src/sequencer_consensus_context_test.rs

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::future::ready;
2-
use std::sync::Arc;
2+
use std::sync::{Arc, LazyLock};
33
use std::time::Duration;
44

55
use apollo_batcher_types::batcher_types::{
@@ -67,6 +67,7 @@ use crate::sequencer_consensus_context::{
6767
SequencerConsensusContext,
6868
SequencerConsensusContextDeps,
6969
};
70+
use crate::snip35::proposal_commitment_from;
7071
use crate::test_utils::{
7172
create_test_and_network_deps,
7273
proposal_init,
@@ -90,7 +91,14 @@ fn expected_l2_gas_info_for_build_proposal_defaults() -> L2GasInfo {
9091
}
9192
use crate::utils::{apply_fee_transformations, make_gas_price_params};
9293

93-
const TEST_PROPOSAL_COMMITMENT: ProposalCommitment = ProposalCommitment(PARTIAL_BLOCK_HASH.0);
94+
static TEST_PROPOSAL_COMMITMENT: LazyLock<ProposalCommitment> = LazyLock::new(|| {
95+
// Default test setup: empty fee_proposals_window → `compute_fee_actual` returns `None` →
96+
// `compute_snip35_fee_proposal` falls back to `self.l2_gas_price` (default
97+
// `min_gas_price` = 8 gwei = 8_000_000_000 FRI). The orchestrator chains this value into
98+
// the proposal commitment via `proposal_commitment_from`. The `proposal_init` test helper
99+
// produces an init with `fee_proposal_fri = Some(8 gwei)` to match what the proposer emits.
100+
proposal_commitment_from(PARTIAL_BLOCK_HASH, Some(GasPrice(8_000_000_000)))
101+
});
94102
const HEIGHT_0: BlockNumber = BlockNumber(0);
95103
const HEIGHT_1: BlockNumber = BlockNumber(1);
96104

@@ -130,7 +138,7 @@ async fn validate_proposal_success() {
130138
let fin_receiver = context
131139
.validate_proposal(proposal_init(HEIGHT_0, ROUND_0), TIMEOUT, content_receiver)
132140
.await;
133-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
141+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
134142
}
135143

136144
#[rstest]
@@ -176,7 +184,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) {
176184
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() });
177185
content_sender.send(transactions.clone()).await.unwrap();
178186
let fin = ProposalPart::Fin(ProposalFin {
179-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
187+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
180188
executed_transaction_count: n_executed_txs_count.try_into().unwrap(),
181189
fin_payload: Some(ProposalFinPayload {
182190
commitment_parts: CommitmentParts::default(),
@@ -186,11 +194,11 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) {
186194
content_sender.send(fin.clone()).await.unwrap();
187195
let fin_receiver = context.validate_proposal(init.clone(), TIMEOUT, content_receiver).await;
188196
content_sender.close_channel();
189-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
197+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
190198

191199
let build_param =
192200
BuildParam { round: ROUND_1, valid_round: Some(ROUND_0), ..Default::default() };
193-
context.repropose(TEST_PROPOSAL_COMMITMENT, build_param).await;
201+
context.repropose(*TEST_PROPOSAL_COMMITMENT, build_param).await;
194202
let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap();
195203
// Reproposal sends init with updated round, proposer, valid_round.
196204
let mut expected_init = init;
@@ -206,7 +214,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) {
206214
assert!(receiver.next().await.is_none());
207215

208216
// Verify decision_reached uses the updated init (from reproposal round) for finalize.
209-
context.decision_reached(HEIGHT_0, ROUND_1, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
217+
context.decision_reached(HEIGHT_0, ROUND_1, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
210218
}
211219

212220
#[tokio::test]
@@ -254,14 +262,14 @@ async fn validate_then_build_then_decision_reached_round_0_uses_round_0_init() {
254262
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() });
255263
content_sender.send(transactions.clone()).await.unwrap();
256264
let fin = ProposalPart::Fin(ProposalFin {
257-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
265+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
258266
executed_transaction_count: TX_BATCH.len().try_into().unwrap(),
259267
fin_payload: None,
260268
});
261269
content_sender.send(fin.clone()).await.unwrap();
262270
let fin_receiver = context.validate_proposal(init, TIMEOUT, content_receiver).await;
263271
content_sender.close_channel();
264-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
272+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
265273

266274
// Round 1: build - clock returns TIMESTAMP_ROUND_1 (different from TIMESTAMP_ROUND_0)
267275
let build_param = BuildParam { round: ROUND_1, ..Default::default() };
@@ -276,10 +284,10 @@ async fn validate_then_build_then_decision_reached_round_0_uses_round_0_init() {
276284
assert_eq!(build_init.timestamp, TIMESTAMP_ROUND_1);
277285
let _txs = receiver.next().await.unwrap();
278286
let _fin = receiver.next().await.unwrap();
279-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
287+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
280288

281289
// Decision reached for round 0 - state_sync should receive TIMESTAMP_ROUND_0
282-
context.decision_reached(HEIGHT_0, ROUND_0, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
290+
context.decision_reached(HEIGHT_0, ROUND_0, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
283291
}
284292

285293
#[tokio::test]
@@ -295,7 +303,7 @@ async fn proposals_from_different_rounds() {
295303
let prop_part_txs =
296304
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() });
297305
let prop_part_fin = ProposalPart::Fin(ProposalFin {
298-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
306+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
299307
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
300308
fin_payload: Some(ProposalFinPayload::default()),
301309
});
@@ -319,7 +327,7 @@ async fn proposals_from_different_rounds() {
319327
let fin_receiver_curr_round = context
320328
.validate_proposal(proposal_init(HEIGHT_0, ROUND_1), TIMEOUT, content_receiver)
321329
.await;
322-
assert_eq!(fin_receiver_curr_round.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
330+
assert_eq!(fin_receiver_curr_round.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
323331

324332
// The proposal from the future round should not be processed.
325333
let (mut content_sender, content_receiver) =
@@ -388,7 +396,7 @@ async fn interrupt_active_proposal() {
388396

389397
// Interrupt active proposal.
390398
assert!(fin_receiver_0.await.is_err());
391-
assert_eq!(fin_receiver_1.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
399+
assert_eq!(fin_receiver_1.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
392400
}
393401

394402
#[tokio::test]
@@ -408,14 +416,16 @@ async fn build_proposal() {
408416
panic!("Expected ProposalPart::Init");
409417
};
410418
assert!(info.timestamp >= before && info.timestamp <= after);
419+
let expected_proposal_commitment =
420+
proposal_commitment_from(PARTIAL_BLOCK_HASH, info.fee_proposal_fri);
411421
assert_eq!(
412422
receiver.next().await.unwrap(),
413423
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() })
414424
);
415425
assert_eq!(
416426
receiver.next().await.unwrap(),
417427
ProposalPart::Fin(ProposalFin {
418-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
428+
proposal_commitment: expected_proposal_commitment,
419429
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
420430
fin_payload: Some(ProposalFinPayload {
421431
commitment_parts: CommitmentParts::default(),
@@ -424,7 +434,7 @@ async fn build_proposal() {
424434
})
425435
);
426436
assert!(receiver.next().await.is_none());
427-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
437+
assert_eq!(fin_receiver.await.unwrap(), expected_proposal_commitment);
428438
}
429439

430440
#[tokio::test]
@@ -558,12 +568,12 @@ async fn propose_then_repropose(#[case] execute_all_txs: bool) {
558568
};
559569
let _txs = receiver.next().await.unwrap();
560570
let fin = receiver.next().await.unwrap();
561-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
571+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
562572

563573
// Re-propose.
564574
let build_param =
565575
BuildParam { round: ROUND_1, valid_round: Some(ROUND_0), ..Default::default() };
566-
context.repropose(TEST_PROPOSAL_COMMITMENT, build_param).await;
576+
context.repropose(*TEST_PROPOSAL_COMMITMENT, build_param).await;
567577
// Re-propose sends the same proposal content but with updated init (round, proposer,
568578
// valid_round).
569579
let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap();
@@ -580,7 +590,7 @@ async fn propose_then_repropose(#[case] execute_all_txs: bool) {
580590
assert!(receiver.next().await.is_none());
581591

582592
// Verify decision_reached uses the updated init (from reproposal round) for finalize.
583-
context.decision_reached(HEIGHT_0, ROUND_1, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
593+
context.decision_reached(HEIGHT_0, ROUND_1, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
584594
}
585595

586596
#[tokio::test]
@@ -679,7 +689,7 @@ async fn gas_price_limits(#[case] maximum: bool) {
679689
// Even though we used the minimum/maximum gas price, not the values we gave the provider,
680690
// the proposal should be still be valid due to the clamping of limit prices.
681691
let fin_receiver = context.validate_proposal(init, TIMEOUT, content_receiver).await;
682-
assert_eq!(fin_receiver.await, Ok(TEST_PROPOSAL_COMMITMENT));
692+
assert_eq!(fin_receiver.await, Ok(*TEST_PROPOSAL_COMMITMENT));
683693
}
684694

685695
#[tokio::test]
@@ -725,7 +735,7 @@ async fn decision_reached_sends_correct_values() {
725735
let _fin = context.build_proposal(BuildParam::default(), TIMEOUT).await.unwrap().await;
726736
// At this point we should have a valid proposal in the context which contains the timestamp.
727737

728-
context.decision_reached(HEIGHT_0, ROUND_0, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
738+
context.decision_reached(HEIGHT_0, ROUND_0, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
729739

730740
let metrics = recorder.handle().render();
731741
CONSENSUS_L2_GAS_PRICE
@@ -885,7 +895,7 @@ async fn oracle_fails_on_startup(#[case] l1_oracle_failure: bool) {
885895
assert_eq!(
886896
receiver.next().await.unwrap(),
887897
ProposalPart::Fin(ProposalFin {
888-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
898+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
889899
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
890900
fin_payload: Some(ProposalFinPayload {
891901
commitment_parts: CommitmentParts::default(),
@@ -894,7 +904,7 @@ async fn oracle_fails_on_startup(#[case] l1_oracle_failure: bool) {
894904
})
895905
);
896906
assert!(receiver.next().await.is_none());
897-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
907+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
898908
}
899909

900910
#[rstest]
@@ -967,7 +977,7 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) {
967977
.validate_proposal(proposal_init(HEIGHT_0, ROUND_0), TIMEOUT, content_receiver)
968978
.await;
969979
let proposal_commitment = fin_receiver.await.unwrap();
970-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
980+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
971981

972982
// Decision reached
973983

@@ -1000,7 +1010,7 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) {
10001010
assert_eq!(
10011011
receiver.next().await.unwrap(),
10021012
ProposalPart::Fin(ProposalFin {
1003-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
1013+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
10041014
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
10051015
fin_payload: Some(ProposalFinPayload {
10061016
commitment_parts: CommitmentParts::default(),
@@ -1009,7 +1019,7 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) {
10091019
})
10101020
);
10111021
assert!(receiver.next().await.is_none());
1012-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
1022+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
10131023
}
10141024

10151025
// L2 gas is a bit above the minimum gas price.
@@ -1136,7 +1146,7 @@ async fn override_prices_behavior(
11361146
return;
11371147
}
11381148

1139-
context.decision_reached(HEIGHT_0, ROUND_0, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
1149+
context.decision_reached(HEIGHT_0, ROUND_0, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
11401150

11411151
let actual_l2_gas_price = context.l2_gas_price.0;
11421152

@@ -1262,7 +1272,7 @@ async fn change_gas_price_overrides() {
12621272
.await;
12631273

12641274
let proposal_commitment = fin_receiver.await.unwrap();
1265-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
1275+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
12661276

12671277
context.decision_reached(HEIGHT_0, ROUND_0, proposal_commitment, false).await.unwrap();
12681278

@@ -1291,7 +1301,7 @@ async fn change_gas_price_overrides() {
12911301
let content_receiver = send_proposal_to_validator_context(&mut context).await;
12921302
let fin_receiver = context.validate_proposal(modified_init, TIMEOUT, content_receiver).await;
12931303
let proposal_commitment = fin_receiver.await.unwrap();
1294-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
1304+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
12951305

12961306
// Validate block number 1, round 1.
12971307
let new_dynamic_config = ContextDynamicConfig {
@@ -1321,7 +1331,7 @@ async fn change_gas_price_overrides() {
13211331
let content_receiver = send_proposal_to_validator_context(&mut context).await;
13221332
let fin_receiver = context.validate_proposal(modified_init, TIMEOUT, content_receiver).await;
13231333
let proposal_commitment = fin_receiver.await.unwrap();
1324-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
1334+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
13251335

13261336
context.decision_reached(HEIGHT_1, ROUND_1, proposal_commitment, false).await.unwrap();
13271337

@@ -1340,7 +1350,7 @@ async fn change_gas_price_overrides() {
13401350
.await
13411351
.unwrap();
13421352

1343-
assert_eq!(fin_receiver, TEST_PROPOSAL_COMMITMENT);
1353+
assert_eq!(fin_receiver, *TEST_PROPOSAL_COMMITMENT);
13441354
let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap();
13451355

13461356
let part = receiver.next().await.unwrap();

0 commit comments

Comments
 (0)