Skip to content

Commit 4e8a975

Browse files
apollo_consensus: update consensus sim test to simulate network msg drops
1 parent dbcb7f4 commit 4e8a975

File tree

1 file changed

+35
-18
lines changed

1 file changed

+35
-18
lines changed

crates/apollo_consensus/src/simulation_test.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ use rand::rngs::StdRng;
1616
use rand::{Rng, SeedableRng};
1717
use starknet_api::block::BlockNumber;
1818
use starknet_types_core::felt::Felt;
19+
use test_case::test_case;
1920

2021
use crate::single_height_consensus::SingleHeightConsensus;
2122
use crate::state_machine::{SMRequest, StateMachineEvent, Step};
2223
use crate::types::{Decision, ProposalCommitment, Round, ValidatorId};
2324
use crate::votes_threshold::QuorumType;
2425

2526
const HEIGHT_0: BlockNumber = BlockNumber(0);
26-
const PROPOSAL_COMMITMENT: ProposalCommitment = ProposalCommitment(Felt::ONE);
2727
const TOTAL_NODES: usize = 100;
2828
const THRESHOLD: usize = (2 * TOTAL_NODES / 3) + 1;
2929
const NODE_0_LEADER_PROBABILITY: f64 = 0.1;
@@ -103,6 +103,12 @@ impl Ord for TimedEvent {
103103
}
104104
}
105105

106+
/// Generates a deterministic commitment for the given round.
107+
/// Each round gets a unique commitment based on the round number.
108+
fn proposal_commitment_for_round(round: Round) -> ProposalCommitment {
109+
ProposalCommitment(Felt::from(u64::from(round)))
110+
}
111+
106112
/// Discrete event simulation for consensus protocol.
107113
///
108114
/// Uses a timeline-based approach where events are scheduled at specific
@@ -128,10 +134,12 @@ struct DiscreteEventSimulation {
128134
num_rounds: usize,
129135
/// Tracks which rounds NODE_0 is the proposer.
130136
node_0_proposer_rounds: HashSet<Round>,
137+
/// The keep ratio for the network (probability that messages are not dropped).
138+
keep_ratio: f64,
131139
}
132140

133141
impl DiscreteEventSimulation {
134-
fn new(total_nodes: usize, seed: u64, num_rounds: usize) -> Self {
142+
fn new(total_nodes: usize, seed: u64, num_rounds: usize, keep_ratio: f64) -> Self {
135143
let rng = StdRng::seed_from_u64(seed);
136144
let validators: Vec<ValidatorId> =
137145
(0..total_nodes).map(|i| ValidatorId::from(u64::try_from(i).unwrap())).collect();
@@ -156,6 +164,7 @@ impl DiscreteEventSimulation {
156164
node_votes: HashMap::new(),
157165
num_rounds,
158166
node_0_proposer_rounds: HashSet::new(),
167+
keep_ratio,
159168
}
160169
}
161170

@@ -180,8 +189,14 @@ impl DiscreteEventSimulation {
180189
}
181190

182191
/// Schedules an event to occur at the specified absolute tick.
192+
/// Internal events are always scheduled.
193+
/// Other events are scheduled with probability keep_ratio.
183194
fn schedule_at_tick(&mut self, tick: u64, event: InputEvent) {
184-
self.pending_events.push(TimedEvent { tick, event });
195+
let should_enqueue =
196+
matches!(event, InputEvent::Internal(_)) || self.rng.gen_bool(self.keep_ratio);
197+
if should_enqueue {
198+
self.pending_events.push(TimedEvent { tick, event });
199+
}
185200
}
186201

187202
/// Pre-generates all events for all requested rounds.
@@ -228,10 +243,10 @@ impl DiscreteEventSimulation {
228243
/// The only real constraint enforced is: precommit_tick > prevote_tick (same voter).
229244
fn schedule_peer_votes(&mut self, round: Round, round_start_tick: u64) {
230245
let round_end_tick = round_start_tick + ROUND_DURATION;
246+
let proposal_commitment = Some(proposal_commitment_for_round(round));
231247
// Skip index 0 (self) - our votes are handled by the state machine
232248
for i in 1..self.validators.len() {
233249
let voter = self.validators[i];
234-
let commitment = Some(PROPOSAL_COMMITMENT);
235250

236251
let prevote_tick = round_start_tick
237252
+ self.rng.gen_range(PREVOTE_ARRIVAL_DELAY_RANGE).min(round_end_tick - 1);
@@ -244,7 +259,7 @@ impl DiscreteEventSimulation {
244259
vote_type: VoteType::Prevote,
245260
height: HEIGHT_0,
246261
round,
247-
proposal_commitment: commitment,
262+
proposal_commitment,
248263
voter,
249264
}),
250265
);
@@ -255,7 +270,7 @@ impl DiscreteEventSimulation {
255270
vote_type: VoteType::Precommit,
256271
height: HEIGHT_0,
257272
round,
258-
proposal_commitment: commitment,
273+
proposal_commitment,
259274
voter,
260275
}),
261276
);
@@ -314,8 +329,9 @@ impl DiscreteEventSimulation {
314329
SMRequest::StartValidateProposal(init) => {
315330
let delay = self.rng.gen_range(VALIDATION_DELAY_RANGE);
316331
let validate_finish_tick = self.current_tick + delay;
332+
let proposal_commitment = Some(proposal_commitment_for_round(init.round));
317333
let result = StateMachineEvent::FinishedValidation(
318-
Some(PROPOSAL_COMMITMENT),
334+
proposal_commitment,
319335
init.round,
320336
None,
321337
);
@@ -324,8 +340,8 @@ impl DiscreteEventSimulation {
324340
SMRequest::StartBuildProposal(round) => {
325341
let delay = self.rng.gen_range(BUILD_PROPOSAL_DELAY_RANGE);
326342
let build_finish_tick = self.current_tick + delay;
327-
let result =
328-
StateMachineEvent::FinishedBuilding(Some(PROPOSAL_COMMITMENT), round);
343+
let proposal_commitment = Some(proposal_commitment_for_round(round));
344+
let result = StateMachineEvent::FinishedBuilding(proposal_commitment, round);
329345
self.schedule_at_tick(build_finish_tick, InputEvent::Internal(result));
330346

331347
// Schedule peer votes after build finish
@@ -384,7 +400,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
384400
(v.vote_type, v.proposal_commitment)
385401
{
386402
let entry = stats.entry(v.round).or_insert_with(|| RoundStats {
387-
expected_commitment: PROPOSAL_COMMITMENT,
403+
expected_commitment: proposal_commitment_for_round(v.round),
388404
..Default::default()
389405
});
390406
if commitment == entry.expected_commitment {
@@ -397,7 +413,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
397413
| InputEvent::Internal(StateMachineEvent::FinishedBuilding(c, r)) => {
398414
if let Some(proposal_commitment) = *c {
399415
let entry = stats.entry(*r).or_insert_with(|| RoundStats {
400-
expected_commitment: PROPOSAL_COMMITMENT,
416+
expected_commitment: proposal_commitment_for_round(*r),
401417
..Default::default()
402418
});
403419
if proposal_commitment == entry.expected_commitment {
@@ -419,7 +435,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
419435
if let Some(s) = stats.get(&r) {
420436
// Check what the node actually voted for in this round
421437
// If the node voted precommit for the valid commitment, count it
422-
let expected_commitment = PROPOSAL_COMMITMENT;
438+
let expected_commitment = proposal_commitment_for_round(r);
423439
let self_vote = sim
424440
.node_votes
425441
.get(&r)
@@ -520,16 +536,17 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
520536
}
521537
}
522538

523-
#[test]
524-
fn test_honest_nodes_only() {
539+
#[test_case(1.0; "keep_all")]
540+
#[test_case(0.7; "keep_70%")]
541+
fn test_honest_nodes_only(keep_ratio: f64) {
525542
let seed = rand::thread_rng().gen();
526-
let num_rounds = 1; // Number of rounds to pre-generate
543+
let num_rounds = 5; // Number of rounds to pre-generate
527544
println!(
528-
"Running consensus simulation with total nodes {TOTAL_NODES}, {num_rounds} rounds, and \
529-
seed: {seed}"
545+
"Running consensus simulation with total nodes {TOTAL_NODES}, {num_rounds} rounds, keep \
546+
ratio {keep_ratio} and seed: {seed}"
530547
);
531548

532-
let mut sim = DiscreteEventSimulation::new(TOTAL_NODES, seed, num_rounds);
549+
let mut sim = DiscreteEventSimulation::new(TOTAL_NODES, seed, num_rounds, keep_ratio);
533550

534551
let deadline_ticks = u64::try_from(num_rounds).unwrap() * ROUND_DURATION;
535552
let result = sim.run(deadline_ticks);

0 commit comments

Comments
 (0)