@@ -15,14 +15,14 @@ use rand::rngs::StdRng;
1515use rand:: { Rng , SeedableRng } ;
1616use starknet_api:: block:: BlockNumber ;
1717use starknet_types_core:: felt:: Felt ;
18+ use test_case:: test_case;
1819
1920use crate :: single_height_consensus:: SingleHeightConsensus ;
2021use crate :: state_machine:: { SMRequest , StateMachineEvent , Step } ;
2122use crate :: types:: { Decision , ProposalCommitment , Round , ValidatorId } ;
2223use crate :: votes_threshold:: QuorumType ;
2324
2425const HEIGHT_0 : BlockNumber = BlockNumber ( 0 ) ;
25- const PROPOSAL_COMMITMENT : ProposalCommitment = ProposalCommitment ( Felt :: ONE ) ;
2626const TOTAL_NODES : usize = 100 ;
2727const THRESHOLD : usize = ( 2 * TOTAL_NODES / 3 ) + 1 ;
2828const 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
122130impl 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