@@ -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 DEADLINE_TICKS : u64 = 200 ;
@@ -74,6 +74,12 @@ impl Ord for TimedEvent {
7474 }
7575}
7676
77+ /// Generates a deterministic commitment for the given round.
78+ /// Each round gets a unique commitment based on the round number.
79+ fn proposal_commitment_for_round ( round : Round ) -> ProposalCommitment {
80+ ProposalCommitment ( Felt :: from ( u64:: from ( round) ) )
81+ }
82+
7783/// Discrete event simulation for consensus protocol.
7884///
7985/// Uses a timeline-based approach where events are scheduled at specific
@@ -100,10 +106,12 @@ struct DiscreteEventSimulation {
100106 build_finish_info : HashMap < Round , ( u64 , Option < ProposalCommitment > ) > ,
101107 /// Tracks what the node actually voted for in each round.
102108 node_votes : HashMap < Round , Vote > ,
109+ /// The keep ratio for the network (probability that messages are not dropped).
110+ keep_ratio : f64 ,
103111}
104112
105113impl DiscreteEventSimulation {
106- fn new ( total_nodes : usize , seed : u64 ) -> Self {
114+ fn new ( total_nodes : usize , seed : u64 , keep_ratio : f64 ) -> Self {
107115 let rng = StdRng :: seed_from_u64 ( seed) ;
108116 let validators: Vec < ValidatorId > =
109117 ( 0 ..total_nodes) . map ( |i| ValidatorId :: from ( u64:: try_from ( i) . unwrap ( ) ) ) . collect ( ) ;
@@ -128,6 +136,7 @@ impl DiscreteEventSimulation {
128136 processed_history : Vec :: new ( ) ,
129137 build_finish_info : HashMap :: new ( ) ,
130138 node_votes : HashMap :: new ( ) ,
139+ keep_ratio,
131140 }
132141 }
133142
@@ -152,11 +161,17 @@ impl DiscreteEventSimulation {
152161 }
153162
154163 /// Schedules an event to occur after the specified delay.
164+ /// Internal events are always scheduled.
165+ /// Other events are scheduled with probability keep_ratio.
155166 fn schedule ( & mut self , delay : u64 , event : InputEvent ) {
156- self . pending_events . push ( TimedEvent { tick : self . current_tick + delay, event } ) ;
167+ let should_enqueue =
168+ matches ! ( event, InputEvent :: Internal ( _) ) || self . rng . gen_bool ( self . keep_ratio ) ;
169+ if should_enqueue {
170+ self . pending_events . push ( TimedEvent { tick : self . current_tick + delay, event } ) ;
171+ }
157172 }
158173
159- /// Generates traffic for a specific round with only honest nodes .
174+ /// Generates traffic for a specific round with a given keep ratio .
160175 ///
161176 /// - Proposer sends: Proposal -> Prevote -> Precommit (in order)
162177 /// - Other validators send: Prevote -> Precommit (in order)
@@ -165,8 +180,8 @@ impl DiscreteEventSimulation {
165180 /// but each node's messages maintain correct ordering.
166181 fn generate_round_traffic ( & mut self , round : Round ) {
167182 let leader_id = Self :: get_leader ( self . seed , round) ;
183+ let proposal_commitment = Some ( proposal_commitment_for_round ( round) ) ;
168184
169- // 1. Proposal from leader (if not self)
170185 if leader_id != * NODE_0 {
171186 let delay = self . rng . gen_range ( 2 ..20 ) ;
172187 self . schedule (
@@ -180,11 +195,8 @@ impl DiscreteEventSimulation {
180195 ) ;
181196 }
182197
183- // 2. Votes from other honest validators
184- // Skip index 0 (self) - our votes are handled by the state machine
185198 for i in 1 ..self . validators . len ( ) {
186199 let voter = self . validators [ i] ;
187- let commitment = Some ( PROPOSAL_COMMITMENT ) ;
188200
189201 // Random delays to simulate network jitter
190202 // TODO(Asmaa): currently ignore the proposal commitment value, since we are in the
@@ -194,26 +206,23 @@ impl DiscreteEventSimulation {
194206 let prevote_delay = base + self . rng . gen_range ( 2 ..20 ) ;
195207 let precommit_delta = self . rng . gen_range ( 5 ..20 ) ;
196208
197- // Schedule prevote
198209 self . schedule (
199210 prevote_delay,
200211 InputEvent :: Vote ( Vote {
201212 vote_type : VoteType :: Prevote ,
202213 height : HEIGHT_0 ,
203214 round,
204- proposal_commitment : commitment ,
215+ proposal_commitment,
205216 voter,
206217 } ) ,
207218 ) ;
208-
209- // Schedule precommit (after prevote)
210219 self . schedule (
211220 prevote_delay + precommit_delta,
212221 InputEvent :: Vote ( Vote {
213222 vote_type : VoteType :: Precommit ,
214223 height : HEIGHT_0 ,
215224 round,
216- proposal_commitment : commitment ,
225+ proposal_commitment,
217226 voter,
218227 } ) ,
219228 ) ;
@@ -271,23 +280,25 @@ impl DiscreteEventSimulation {
271280 ///
272281 /// This simulates the manager's role in handling consensus requests,
273282 /// such as validation results, proposal building, and timeouts.
283+ /// Also tracks BroadcastVote requests to know what the node actually voted for.
274284 fn handle_requests ( & mut self , reqs : VecDeque < SMRequest > ) -> Option < Decision > {
275285 for req in reqs {
276286 match req {
277287 SMRequest :: StartValidateProposal ( init) => {
278288 let delay = self . rng . gen_range ( 15 ..30 ) ;
289+ let proposal_commitment = Some ( proposal_commitment_for_round ( init. round ) ) ;
279290 let result = StateMachineEvent :: FinishedValidation (
280- Some ( PROPOSAL_COMMITMENT ) ,
291+ proposal_commitment ,
281292 init. round ,
282293 None ,
283294 ) ;
284295 self . schedule ( delay, InputEvent :: Internal ( result) ) ;
285296 }
286297 SMRequest :: StartBuildProposal ( round) => {
287298 let delay = self . rng . gen_range ( 15 ..30 ) ;
288- let result =
289- StateMachineEvent :: FinishedBuilding ( Some ( PROPOSAL_COMMITMENT ) , round) ;
290- self . build_finish_info . insert ( round, ( delay, Some ( PROPOSAL_COMMITMENT ) ) ) ;
299+ let proposal_commitment = Some ( proposal_commitment_for_round ( round ) ) ;
300+ let result = StateMachineEvent :: FinishedBuilding ( proposal_commitment , round) ;
301+ self . build_finish_info . insert ( round, ( delay, proposal_commitment ) ) ;
291302 self . schedule ( delay, InputEvent :: Internal ( result) ) ;
292303 }
293304 SMRequest :: ScheduleTimeout ( step, round) => {
@@ -338,7 +349,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
338349 if v. vote_type == VoteType :: Precommit {
339350 if let Some ( proposal_commitment) = v. proposal_commitment {
340351 let entry = stats. entry ( v. round ) . or_insert_with ( || RoundStats {
341- expected_commitment : PROPOSAL_COMMITMENT ,
352+ expected_commitment : proposal_commitment_for_round ( v . round ) ,
342353 ..Default :: default ( )
343354 } ) ;
344355 if proposal_commitment == entry. expected_commitment {
@@ -352,7 +363,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
352363 | InputEvent :: Internal ( StateMachineEvent :: FinishedBuilding ( c, r) ) => {
353364 if let Some ( proposal_commitment) = * c {
354365 let entry = stats. entry ( * r) . or_insert_with ( || RoundStats {
355- expected_commitment : PROPOSAL_COMMITMENT ,
366+ expected_commitment : proposal_commitment_for_round ( * r ) ,
356367 ..Default :: default ( )
357368 } ) ;
358369 if proposal_commitment == entry. expected_commitment {
@@ -373,7 +384,7 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
373384 if let Some ( s) = stats. get ( & r) {
374385 // Check what the node actually voted for in this round
375386 // If the node voted precommit for the valid commitment, count it
376- let expected_commitment = PROPOSAL_COMMITMENT ;
387+ let expected_commitment = proposal_commitment_for_round ( r ) ;
377388 let self_vote = sim. node_votes . get ( & r) . map_or ( 0 , |v| {
378389 if v. vote_type == VoteType :: Precommit
379390 && v. proposal_commitment == Some ( expected_commitment)
@@ -475,12 +486,16 @@ fn verify_result(sim: &DiscreteEventSimulation, result: Option<&Decision>) {
475486 }
476487}
477488
478- #[ test]
479- fn test_honest_nodes_only ( ) {
489+ #[ test_case( 1.0 ; "keep_all" ) ]
490+ #[ test_case( 0.7 ; "keep_70%" ) ]
491+ fn test_honest_nodes_only ( keep_ratio : f64 ) {
480492 let seed = rand:: thread_rng ( ) . gen ( ) ;
481- println ! ( "Running consensus simulation with total nodes {TOTAL_NODES} and seed: {seed}" ) ;
493+ println ! (
494+ "Running consensus simulation with total nodes {TOTAL_NODES}, keep ratio {keep_ratio} and \
495+ seed: {seed}"
496+ ) ;
482497
483- let mut sim = DiscreteEventSimulation :: new ( TOTAL_NODES , seed) ;
498+ let mut sim = DiscreteEventSimulation :: new ( TOTAL_NODES , seed, keep_ratio ) ;
484499
485500 let result = sim. run ( DEADLINE_TICKS ) ;
486501
0 commit comments