Skip to content

Commit 4af840c

Browse files
apollo_consensus: update consensus sim test to simulate network msg drops
1 parent 8cfadb5 commit 4af840c

File tree

1 file changed

+48
-23
lines changed

1 file changed

+48
-23
lines changed

crates/apollo_consensus/src/simulation_test.rs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ use rand::rngs::StdRng;
1515
use rand::{Rng, SeedableRng};
1616
use starknet_api::block::BlockNumber;
1717
use starknet_types_core::felt::Felt;
18+
use test_case::test_case;
1819

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

2425
const HEIGHT_0: BlockNumber = BlockNumber(0);
25-
const PROPOSAL_COMMITMENT: ProposalCommitment = ProposalCommitment(Felt::ONE);
2626
const TOTAL_NODES: usize = 100;
2727
const THRESHOLD: usize = (2 * TOTAL_NODES / 3) + 1;
2828
const NODE_0_LEADER_PROBABILITY: f64 = 0.1;
@@ -92,6 +92,12 @@ impl Ord for TimedEvent {
9292
}
9393
}
9494

95+
/// Generates a deterministic commitment for the given round.
96+
/// Each round gets a unique commitment based on the round number.
97+
fn proposal_commitment_for_round(round: Round) -> ProposalCommitment {
98+
ProposalCommitment(Felt::from(u64::from(round)))
99+
}
100+
95101
/// Discrete event simulation for consensus protocol.
96102
///
97103
/// Uses a timeline-based approach where events are scheduled at specific
@@ -117,10 +123,12 @@ struct DiscreteEventSimulation {
117123
num_rounds: usize,
118124
/// Tracks which rounds NODE_0 is the proposer.
119125
node_0_proposer_rounds: HashSet<Round>,
126+
/// The keep ratio for the network (probability that messages are not dropped).
127+
keep_ratio: f64,
120128
}
121129

122130
impl DiscreteEventSimulation {
123-
fn new(total_nodes: usize, seed: u64, num_rounds: usize) -> Self {
131+
fn new(total_nodes: usize, seed: u64, num_rounds: usize, keep_ratio: f64) -> Self {
124132
let rng = StdRng::seed_from_u64(seed);
125133
let validators: Vec<ValidatorId> =
126134
(0..total_nodes).map(|i| ValidatorId::from(u64::try_from(i).unwrap())).collect();
@@ -145,6 +153,7 @@ impl DiscreteEventSimulation {
145153
node_votes: HashMap::new(),
146154
num_rounds,
147155
node_0_proposer_rounds: HashSet::new(),
156+
keep_ratio,
148157
}
149158
}
150159

@@ -169,8 +178,14 @@ impl DiscreteEventSimulation {
169178
}
170179

171180
/// Schedules an event to occur at the specified absolute tick.
181+
/// Internal events are always scheduled.
182+
/// Other events are scheduled with probability keep_ratio.
172183
fn schedule_at_tick(&mut self, tick: u64, event: InputEvent) {
173-
self.pending_events.push(TimedEvent { tick, event });
184+
let should_enqueue =
185+
matches!(event, InputEvent::Internal(_)) || self.rng.gen_bool(self.keep_ratio);
186+
if should_enqueue {
187+
self.pending_events.push(TimedEvent { tick, event });
188+
}
174189
}
175190

176191
/// Pre-generates all events for all requested rounds.
@@ -214,7 +229,7 @@ impl DiscreteEventSimulation {
214229
if leader_id != *NODE_0 {
215230
for i in 1..self.validators.len() {
216231
let voter = self.validators[i];
217-
let commitment = Some(PROPOSAL_COMMITMENT);
232+
let proposal_commitment = Some(proposal_commitment_for_round(round));
218233

219234
let prevote_tick = (round_start_tick
220235
+ self.rng.gen_range(PREVOTE_ARRIVAL_DELAY.0..PREVOTE_ARRIVAL_DELAY.1))
@@ -231,7 +246,7 @@ impl DiscreteEventSimulation {
231246
vote_type: VoteType::Prevote,
232247
height: HEIGHT_0,
233248
round,
234-
proposal_commitment: commitment,
249+
proposal_commitment,
235250
voter,
236251
}),
237252
);
@@ -242,7 +257,7 @@ impl DiscreteEventSimulation {
242257
vote_type: VoteType::Precommit,
243258
height: HEIGHT_0,
244259
round,
245-
proposal_commitment: commitment,
260+
proposal_commitment,
246261
voter,
247262
}),
248263
);
@@ -253,10 +268,14 @@ impl DiscreteEventSimulation {
253268

254269
/// Schedules honest peer votes for a round where NODE_0 is the proposer.
255270
/// These votes should arrive after the build finish event.
256-
fn schedule_honest_peer_votes_after_build(&mut self, round: Round, build_finish_tick: u64) {
271+
fn schedule_honest_peer_votes_after_build(
272+
&mut self,
273+
round: Round,
274+
build_finish_tick: u64,
275+
proposal_commitment: Option<ProposalCommitment>,
276+
) {
257277
for i in 1..self.validators.len() {
258278
let voter = self.validators[i];
259-
let commitment = Some(PROPOSAL_COMMITMENT);
260279

261280
// Votes arrive after build finish with some jitter
262281
let prevote_tick = build_finish_tick
@@ -272,7 +291,7 @@ impl DiscreteEventSimulation {
272291
vote_type: VoteType::Prevote,
273292
height: HEIGHT_0,
274293
round,
275-
proposal_commitment: commitment,
294+
proposal_commitment,
276295
voter,
277296
}),
278297
);
@@ -283,7 +302,7 @@ impl DiscreteEventSimulation {
283302
vote_type: VoteType::Precommit,
284303
height: HEIGHT_0,
285304
round,
286-
proposal_commitment: commitment,
305+
proposal_commitment,
287306
voter,
288307
}),
289308
);
@@ -342,8 +361,9 @@ impl DiscreteEventSimulation {
342361
SMRequest::StartValidateProposal(init) => {
343362
let delay = self.rng.gen_range(VALIDATION_DELAY.0..VALIDATION_DELAY.1);
344363
let validate_finish_tick = self.current_tick + delay;
364+
let proposal_commitment = Some(proposal_commitment_for_round(init.round));
345365
let result = StateMachineEvent::FinishedValidation(
346-
Some(PROPOSAL_COMMITMENT),
366+
proposal_commitment,
347367
init.round,
348368
None,
349369
);
@@ -352,13 +372,17 @@ impl DiscreteEventSimulation {
352372
SMRequest::StartBuildProposal(round) => {
353373
let delay = self.rng.gen_range(BUILD_PROPOSAL_DELAY.0..BUILD_PROPOSAL_DELAY.1);
354374
let build_finish_tick = self.current_tick + delay;
355-
let result =
356-
StateMachineEvent::FinishedBuilding(Some(PROPOSAL_COMMITMENT), round);
375+
let proposal_commitment = Some(proposal_commitment_for_round(round));
376+
let result = StateMachineEvent::FinishedBuilding(proposal_commitment, round);
357377
self.schedule_at_tick(build_finish_tick, InputEvent::Internal(result));
358378

359379
// If NODE_0 is proposer for this round, schedule peer votes after build finish
360380
if self.node_0_proposer_rounds.contains(&round) {
361-
self.schedule_honest_peer_votes_after_build(round, build_finish_tick);
381+
self.schedule_honest_peer_votes_after_build(
382+
round,
383+
build_finish_tick,
384+
proposal_commitment,
385+
);
362386
}
363387
}
364388
SMRequest::ScheduleTimeout(step, round) => {
@@ -413,7 +437,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
413437
if v.vote_type == VoteType::Precommit {
414438
if let Some(proposal_commitment) = v.proposal_commitment {
415439
let entry = stats.entry(v.round).or_insert_with(|| RoundStats {
416-
expected_commitment: PROPOSAL_COMMITMENT,
440+
expected_commitment: proposal_commitment_for_round(v.round),
417441
..Default::default()
418442
});
419443
if proposal_commitment == entry.expected_commitment {
@@ -427,7 +451,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
427451
| InputEvent::Internal(StateMachineEvent::FinishedBuilding(c, r)) => {
428452
if let Some(proposal_commitment) = *c {
429453
let entry = stats.entry(*r).or_insert_with(|| RoundStats {
430-
expected_commitment: PROPOSAL_COMMITMENT,
454+
expected_commitment: proposal_commitment_for_round(*r),
431455
..Default::default()
432456
});
433457
if proposal_commitment == entry.expected_commitment {
@@ -449,7 +473,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
449473
if let Some(s) = stats.get(&r) {
450474
// Check what the node actually voted for in this round
451475
// If the node voted precommit for the valid commitment, count it
452-
let expected_commitment = PROPOSAL_COMMITMENT;
476+
let expected_commitment = proposal_commitment_for_round(r);
453477
let self_vote = sim.node_votes.get(&r).map_or(0, |v| {
454478
if v.vote_type == VoteType::Precommit
455479
&& v.proposal_commitment == Some(expected_commitment)
@@ -551,16 +575,17 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
551575
}
552576
}
553577

554-
#[test]
555-
fn test_honest_nodes_only() {
578+
#[test_case(1.0; "keep_all")]
579+
#[test_case(0.7; "keep_70%")]
580+
fn test_honest_nodes_only(keep_ratio: f64) {
556581
let seed = rand::thread_rng().gen();
557-
let num_rounds = 1; // Number of rounds to pre-generate
582+
let num_rounds = 5; // Number of rounds to pre-generate
558583
println!(
559-
"Running consensus simulation with total nodes {TOTAL_NODES}, {num_rounds} rounds, and \
560-
seed: {seed}"
584+
"Running consensus simulation with total nodes {TOTAL_NODES}, {num_rounds} rounds, keep \
585+
ratio {keep_ratio} and seed: {seed}"
561586
);
562587

563-
let mut sim = DiscreteEventSimulation::new(TOTAL_NODES, seed, num_rounds);
588+
let mut sim = DiscreteEventSimulation::new(TOTAL_NODES, seed, num_rounds, keep_ratio);
564589

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

0 commit comments

Comments
 (0)