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