Skip to content

Commit e9102f1

Browse files
apollo_consensus_orchestrator: include fee_proposal_fri in proposal commitment hash (#13949)
1 parent 2f9934b commit e9102f1

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
@@ -103,6 +103,7 @@ use crate::snip35::{
103103
compute_fee_actual,
104104
compute_fee_proposal,
105105
compute_fee_target,
106+
proposal_commitment_from,
106107
FeeProposalInfo,
107108
FEE_PROPOSAL_MARGIN_PPT,
108109
FEE_PROPOSAL_WINDOW_SIZE,
@@ -184,8 +185,10 @@ impl BuiltProposals {
184185
proposal_id: &ProposalId,
185186
finished_info: FinishedProposalInfo,
186187
) {
187-
let proposal_commitment =
188-
ProposalCommitment(finished_info.proposal_commitment.partial_block_hash.0);
188+
let proposal_commitment = proposal_commitment_from(
189+
finished_info.proposal_commitment.partial_block_hash,
190+
init.fee_proposal_fri,
191+
);
189192

190193
let height = init.height;
191194
let round = init.round;
@@ -591,9 +594,13 @@ impl SequencerConsensusContext {
591594
compiled_class_hashes_for_migration: central_objects
592595
.compiled_class_hashes_for_migration,
593596
proposal_commitment: commitment,
597+
// TODO(SNIP-35): plumb the parent block's `fee_proposal_fri` here once
598+
// `central_objects.parent_proposal_commitment` carries it (or read from
599+
// BlockHeaderWithoutHash storage). Today we pass `None`, which means
600+
// pre-V0_14_3 commitments — correct for non-SNIP-35 deployments only.
594601
parent_proposal_commitment: central_objects
595602
.parent_proposal_commitment
596-
.map(|commitment| ProposalCommitment(commitment.partial_block_hash.0)),
603+
.map(|c| proposal_commitment_from(c.partial_block_hash, None)),
597604
recent_block_hashes: self.collect_recent_block_hashes(height).await,
598605
})
599606
.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,6 +1,6 @@
11
use std::collections::BTreeMap;
22
use std::future::ready;
3-
use std::sync::Arc;
3+
use std::sync::{Arc, LazyLock};
44
use std::time::Duration;
55

66
use apollo_batcher_types::batcher_types::{
@@ -72,6 +72,7 @@ use crate::sequencer_consensus_context::{
7272
SequencerConsensusContext,
7373
SequencerConsensusContextDeps,
7474
};
75+
use crate::snip35::proposal_commitment_from;
7576
use crate::test_utils::{
7677
create_test_and_network_deps,
7778
proposal_init,
@@ -96,7 +97,14 @@ fn expected_l2_gas_info_for_build_proposal_defaults() -> L2GasInfo {
9697
use crate::snip35::{compute_fee_actual, FEE_PROPOSAL_WINDOW_SIZE};
9798
use crate::utils::{apply_fee_transformations, make_gas_price_params};
9899

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

@@ -136,7 +144,7 @@ async fn validate_proposal_success() {
136144
let fin_receiver = context
137145
.validate_proposal(proposal_init(HEIGHT_0, ROUND_0), TIMEOUT, content_receiver)
138146
.await;
139-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
147+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
140148
}
141149

142150
#[rstest]
@@ -182,7 +190,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) {
182190
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() });
183191
content_sender.send(transactions.clone()).await.unwrap();
184192
let fin = ProposalPart::Fin(ProposalFin {
185-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
193+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
186194
executed_transaction_count: n_executed_txs_count.try_into().unwrap(),
187195
fin_payload: Some(ProposalFinPayload {
188196
commitment_parts: CommitmentParts::default(),
@@ -192,11 +200,11 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) {
192200
content_sender.send(fin.clone()).await.unwrap();
193201
let fin_receiver = context.validate_proposal(init.clone(), TIMEOUT, content_receiver).await;
194202
content_sender.close_channel();
195-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
203+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
196204

197205
let build_param =
198206
BuildParam { round: ROUND_1, valid_round: Some(ROUND_0), ..Default::default() };
199-
context.repropose(TEST_PROPOSAL_COMMITMENT, build_param).await;
207+
context.repropose(*TEST_PROPOSAL_COMMITMENT, build_param).await;
200208
let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap();
201209
// Reproposal sends init with updated round, proposer, valid_round.
202210
let mut expected_init = init;
@@ -212,7 +220,7 @@ async fn validate_then_repropose(#[case] execute_all_txs: bool) {
212220
assert!(receiver.next().await.is_none());
213221

214222
// Verify decision_reached uses the updated init (from reproposal round) for finalize.
215-
context.decision_reached(HEIGHT_0, ROUND_1, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
223+
context.decision_reached(HEIGHT_0, ROUND_1, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
216224
}
217225

218226
#[tokio::test]
@@ -260,14 +268,14 @@ async fn validate_then_build_then_decision_reached_round_0_uses_round_0_init() {
260268
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() });
261269
content_sender.send(transactions.clone()).await.unwrap();
262270
let fin = ProposalPart::Fin(ProposalFin {
263-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
271+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
264272
executed_transaction_count: TX_BATCH.len().try_into().unwrap(),
265273
fin_payload: None,
266274
});
267275
content_sender.send(fin.clone()).await.unwrap();
268276
let fin_receiver = context.validate_proposal(init, TIMEOUT, content_receiver).await;
269277
content_sender.close_channel();
270-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
278+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
271279

272280
// Round 1: build - clock returns TIMESTAMP_ROUND_1 (different from TIMESTAMP_ROUND_0)
273281
let build_param = BuildParam { round: ROUND_1, ..Default::default() };
@@ -282,10 +290,10 @@ async fn validate_then_build_then_decision_reached_round_0_uses_round_0_init() {
282290
assert_eq!(build_init.timestamp, TIMESTAMP_ROUND_1);
283291
let _txs = receiver.next().await.unwrap();
284292
let _fin = receiver.next().await.unwrap();
285-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
293+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
286294

287295
// Decision reached for round 0 - state_sync should receive TIMESTAMP_ROUND_0
288-
context.decision_reached(HEIGHT_0, ROUND_0, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
296+
context.decision_reached(HEIGHT_0, ROUND_0, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
289297
}
290298

291299
#[tokio::test]
@@ -301,7 +309,7 @@ async fn proposals_from_different_rounds() {
301309
let prop_part_txs =
302310
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() });
303311
let prop_part_fin = ProposalPart::Fin(ProposalFin {
304-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
312+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
305313
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
306314
fin_payload: Some(ProposalFinPayload::default()),
307315
});
@@ -325,7 +333,7 @@ async fn proposals_from_different_rounds() {
325333
let fin_receiver_curr_round = context
326334
.validate_proposal(proposal_init(HEIGHT_0, ROUND_1), TIMEOUT, content_receiver)
327335
.await;
328-
assert_eq!(fin_receiver_curr_round.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
336+
assert_eq!(fin_receiver_curr_round.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
329337

330338
// The proposal from the future round should not be processed.
331339
let (mut content_sender, content_receiver) =
@@ -394,7 +402,7 @@ async fn interrupt_active_proposal() {
394402

395403
// Interrupt active proposal.
396404
assert!(fin_receiver_0.await.is_err());
397-
assert_eq!(fin_receiver_1.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
405+
assert_eq!(fin_receiver_1.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
398406
}
399407

400408
#[tokio::test]
@@ -414,14 +422,16 @@ async fn build_proposal() {
414422
panic!("Expected ProposalPart::Init");
415423
};
416424
assert!(info.timestamp >= before && info.timestamp <= after);
425+
let expected_proposal_commitment =
426+
proposal_commitment_from(PARTIAL_BLOCK_HASH, info.fee_proposal_fri);
417427
assert_eq!(
418428
receiver.next().await.unwrap(),
419429
ProposalPart::Transactions(TransactionBatch { transactions: TX_BATCH.to_vec() })
420430
);
421431
assert_eq!(
422432
receiver.next().await.unwrap(),
423433
ProposalPart::Fin(ProposalFin {
424-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
434+
proposal_commitment: expected_proposal_commitment,
425435
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
426436
fin_payload: Some(ProposalFinPayload {
427437
commitment_parts: CommitmentParts::default(),
@@ -430,7 +440,7 @@ async fn build_proposal() {
430440
})
431441
);
432442
assert!(receiver.next().await.is_none());
433-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
443+
assert_eq!(fin_receiver.await.unwrap(), expected_proposal_commitment);
434444
}
435445

436446
#[tokio::test]
@@ -564,12 +574,12 @@ async fn propose_then_repropose(#[case] execute_all_txs: bool) {
564574
};
565575
let _txs = receiver.next().await.unwrap();
566576
let fin = receiver.next().await.unwrap();
567-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
577+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
568578

569579
// Re-propose.
570580
let build_param =
571581
BuildParam { round: ROUND_1, valid_round: Some(ROUND_0), ..Default::default() };
572-
context.repropose(TEST_PROPOSAL_COMMITMENT, build_param).await;
582+
context.repropose(*TEST_PROPOSAL_COMMITMENT, build_param).await;
573583
// Re-propose sends the same proposal content but with updated init (round, proposer,
574584
// valid_round).
575585
let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap();
@@ -586,7 +596,7 @@ async fn propose_then_repropose(#[case] execute_all_txs: bool) {
586596
assert!(receiver.next().await.is_none());
587597

588598
// Verify decision_reached uses the updated init (from reproposal round) for finalize.
589-
context.decision_reached(HEIGHT_0, ROUND_1, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
599+
context.decision_reached(HEIGHT_0, ROUND_1, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
590600
}
591601

592602
#[tokio::test]
@@ -685,7 +695,7 @@ async fn gas_price_limits(#[case] maximum: bool) {
685695
// Even though we used the minimum/maximum gas price, not the values we gave the provider,
686696
// the proposal should be still be valid due to the clamping of limit prices.
687697
let fin_receiver = context.validate_proposal(init, TIMEOUT, content_receiver).await;
688-
assert_eq!(fin_receiver.await, Ok(TEST_PROPOSAL_COMMITMENT));
698+
assert_eq!(fin_receiver.await, Ok(*TEST_PROPOSAL_COMMITMENT));
689699
}
690700

691701
#[tokio::test]
@@ -731,7 +741,7 @@ async fn decision_reached_sends_correct_values() {
731741
let _fin = context.build_proposal(BuildParam::default(), TIMEOUT).await.unwrap().await;
732742
// At this point we should have a valid proposal in the context which contains the timestamp.
733743

734-
context.decision_reached(HEIGHT_0, ROUND_0, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
744+
context.decision_reached(HEIGHT_0, ROUND_0, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
735745

736746
let metrics = recorder.handle().render();
737747
CONSENSUS_L2_GAS_PRICE
@@ -891,7 +901,7 @@ async fn oracle_fails_on_startup(#[case] l1_oracle_failure: bool) {
891901
assert_eq!(
892902
receiver.next().await.unwrap(),
893903
ProposalPart::Fin(ProposalFin {
894-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
904+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
895905
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
896906
fin_payload: Some(ProposalFinPayload {
897907
commitment_parts: CommitmentParts::default(),
@@ -900,7 +910,7 @@ async fn oracle_fails_on_startup(#[case] l1_oracle_failure: bool) {
900910
})
901911
);
902912
assert!(receiver.next().await.is_none());
903-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
913+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
904914
}
905915

906916
#[rstest]
@@ -973,7 +983,7 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) {
973983
.validate_proposal(proposal_init(HEIGHT_0, ROUND_0), TIMEOUT, content_receiver)
974984
.await;
975985
let proposal_commitment = fin_receiver.await.unwrap();
976-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
986+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
977987

978988
// Decision reached
979989

@@ -1006,7 +1016,7 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) {
10061016
assert_eq!(
10071017
receiver.next().await.unwrap(),
10081018
ProposalPart::Fin(ProposalFin {
1009-
proposal_commitment: TEST_PROPOSAL_COMMITMENT,
1019+
proposal_commitment: *TEST_PROPOSAL_COMMITMENT,
10101020
executed_transaction_count: INTERNAL_TX_BATCH.len().try_into().unwrap(),
10111021
fin_payload: Some(ProposalFinPayload {
10121022
commitment_parts: CommitmentParts::default(),
@@ -1015,7 +1025,7 @@ async fn oracle_fails_on_second_block(#[case] l1_oracle_failure: bool) {
10151025
})
10161026
);
10171027
assert!(receiver.next().await.is_none());
1018-
assert_eq!(fin_receiver.await.unwrap(), TEST_PROPOSAL_COMMITMENT);
1028+
assert_eq!(fin_receiver.await.unwrap(), *TEST_PROPOSAL_COMMITMENT);
10191029
}
10201030

10211031
// L2 gas is a bit above the minimum gas price.
@@ -1142,7 +1152,7 @@ async fn override_prices_behavior(
11421152
return;
11431153
}
11441154

1145-
context.decision_reached(HEIGHT_0, ROUND_0, TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
1155+
context.decision_reached(HEIGHT_0, ROUND_0, *TEST_PROPOSAL_COMMITMENT, false).await.unwrap();
11461156

11471157
let actual_l2_gas_price = context.l2_gas_price.0;
11481158

@@ -1268,7 +1278,7 @@ async fn change_gas_price_overrides() {
12681278
.await;
12691279

12701280
let proposal_commitment = fin_receiver.await.unwrap();
1271-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
1281+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
12721282

12731283
context.decision_reached(HEIGHT_0, ROUND_0, proposal_commitment, false).await.unwrap();
12741284

@@ -1297,7 +1307,7 @@ async fn change_gas_price_overrides() {
12971307
let content_receiver = send_proposal_to_validator_context(&mut context).await;
12981308
let fin_receiver = context.validate_proposal(modified_init, TIMEOUT, content_receiver).await;
12991309
let proposal_commitment = fin_receiver.await.unwrap();
1300-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
1310+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
13011311

13021312
// Validate block number 1, round 1.
13031313
let new_dynamic_config = ContextDynamicConfig {
@@ -1328,7 +1338,7 @@ async fn change_gas_price_overrides() {
13281338
let content_receiver = send_proposal_to_validator_context(&mut context).await;
13291339
let fin_receiver = context.validate_proposal(modified_init, TIMEOUT, content_receiver).await;
13301340
let proposal_commitment = fin_receiver.await.unwrap();
1331-
assert_eq!(proposal_commitment, TEST_PROPOSAL_COMMITMENT);
1341+
assert_eq!(proposal_commitment, *TEST_PROPOSAL_COMMITMENT);
13321342

13331343
context.decision_reached(HEIGHT_1, ROUND_1, proposal_commitment, false).await.unwrap();
13341344

@@ -1347,7 +1357,7 @@ async fn change_gas_price_overrides() {
13471357
.await
13481358
.unwrap();
13491359

1350-
assert_eq!(fin_receiver, TEST_PROPOSAL_COMMITMENT);
1360+
assert_eq!(fin_receiver, *TEST_PROPOSAL_COMMITMENT);
13511361
let (_, mut receiver) = network.outbound_proposal_receiver.next().await.unwrap();
13521362

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

0 commit comments

Comments
 (0)