diff --git a/src/fullnode.rs b/src/fullnode.rs index 7259719d39..1682546c25 100644 --- a/src/fullnode.rs +++ b/src/fullnode.rs @@ -936,13 +936,10 @@ mod tests { // transition to a validator. info!("Unpause the Tvu"); pause_tvu.store(false, Ordering::Relaxed); - let expected_rotations = vec![ - (FullnodeReturnType::LeaderToLeaderRotation, ticks_per_slot), - ( - FullnodeReturnType::LeaderToValidatorRotation, - 2 * ticks_per_slot, - ), - ]; + let expected_rotations = vec![( + FullnodeReturnType::LeaderToValidatorRotation, + ticks_per_slot, + )]; for expected_rotation in expected_rotations { loop { diff --git a/src/leader_scheduler.rs b/src/leader_scheduler.rs index 46b4a929ae..1fcf558a3d 100644 --- a/src/leader_scheduler.rs +++ b/src/leader_scheduler.rs @@ -145,16 +145,16 @@ impl LeaderScheduler { tick_height, epoch, ); - if epoch < self.current_epoch { - return; + + if tick_height == 0 { + // Special case: tick_height starts at 0 instead of -1, so generate_schedule() for 0 + // here before moving on to tick_height + 1 + self.generate_schedule(0, bank); } - // Leader schedule is computed at tick 0 (for bootstrap) and then on the second tick 1 for - // the current slot, so that generally the schedule applies to the range [slot N tick 1, - // slot N+1 tick 0). The schedule is shifted right 1 tick from the slot rotation interval so that - // the next leader is always known *before* a rotation occurs - if tick_height == 0 || tick_height % self.ticks_per_epoch == 1 { - self.generate_schedule(tick_height, bank); + // If we're about to cross an epoch boundary generate the schedule for the next epoch + if self.tick_height_to_epoch(tick_height + 1) == epoch + 1 { + self.generate_schedule(tick_height + 1, bank); } } @@ -239,11 +239,7 @@ impl LeaderScheduler { // Updates the leader schedule to include ticks from tick_height to the first tick of the next epoch fn generate_schedule(&mut self, tick_height: u64, bank: &Bank) { - let epoch = if tick_height == 0 { - 0 - } else { - self.tick_height_to_epoch(tick_height) + 1 - }; + let epoch = self.tick_height_to_epoch(tick_height); trace!( "generate_schedule: tick_height={} (epoch={})", tick_height, @@ -545,12 +541,10 @@ pub mod tests { // The leader outside of the newly generated schedule window: // (0, ticks_per_epoch] - info!("yyy"); assert_eq!( leader_scheduler.get_leader_for_slot(0), Some(genesis_block.bootstrap_leader_id) ); - info!("xxxx"); assert_eq!( leader_scheduler .get_leader_for_slot(leader_scheduler.tick_height_to_slot(ticks_per_epoch)), @@ -559,12 +553,12 @@ pub mod tests { // Generate schedule for second epoch. This schedule won't be used but the schedule for // the third epoch cannot be generated without an existing schedule for the second epoch - leader_scheduler.generate_schedule(1, &bank); + leader_scheduler.generate_schedule(ticks_per_epoch, &bank); // Generate schedule for third epoch to ensure the bootstrap leader will not be added to // the schedule, as the bootstrap leader did not vote in the second epoch but all other // validators did - leader_scheduler.generate_schedule(ticks_per_epoch + 1, &bank); + leader_scheduler.generate_schedule(2 * ticks_per_epoch, &bank); // For the next ticks_per_epoch entries, call get_leader_for_slot every // ticks_per_slot entries, and the next leader should be the next validator @@ -993,11 +987,11 @@ pub mod tests { } assert_eq!(leader_scheduler.current_epoch, 0); leader_scheduler.generate_schedule(1, &bank); - assert_eq!(leader_scheduler.current_epoch, 1); + assert_eq!(leader_scheduler.current_epoch, 0); for i in 0..=num_validators { info!("i === {}", i); leader_scheduler.generate_schedule((i + 1) * active_window_tick_length, &bank); - assert_eq!(leader_scheduler.current_epoch, i + 2); + assert_eq!(leader_scheduler.current_epoch, i + 1); if i == 0 { assert_eq!( vec![genesis_block.bootstrap_leader_id], @@ -1096,31 +1090,42 @@ pub mod tests { assert_eq!(bank.tick_height(), 0); // - // tick_height == 0 is a special case - // - leader_scheduler.update_tick_height(0, &bank); - assert_eq!(leader_scheduler.current_epoch, 0); - // The schedule for epoch 0 is known - assert_eq!( - leader_scheduler.get_leader_for_slot(0), - Some(genesis_block.bootstrap_leader_id) - ); - assert_eq!( - leader_scheduler.get_leader_for_slot(1), - Some(genesis_block.bootstrap_leader_id) - ); - // The schedule for epoch 1 is unknown - assert_eq!(leader_scheduler.get_leader_for_slot(2), None,); - - // - // Check various tick heights in epoch 0, and tick 0 of epoch 1 + // Check various tick heights in epoch 0 up to the last tick // for tick_height in &[ + 0, 1, ticks_per_slot, ticks_per_slot + 1, + ticks_per_epoch - 2, + ] { + info!("Checking tick_height {}", *tick_height); + leader_scheduler.update_tick_height(*tick_height, &bank); + assert_eq!(leader_scheduler.current_epoch, 0); + // The schedule for epoch 0 is known + assert_eq!( + leader_scheduler.get_leader_for_slot(0), + Some(genesis_block.bootstrap_leader_id) + ); + assert_eq!( + leader_scheduler.get_leader_for_slot(1), + Some(genesis_block.bootstrap_leader_id) + ); + // The schedule for epoch 1 is unknown + assert_eq!(leader_scheduler.get_leader_for_slot(2), None); + } + + // + // Check the last tick of epoch 0, various tick heights in epoch 1 up to the last tick + // + for tick_height in &[ ticks_per_epoch - 1, ticks_per_epoch, + ticks_per_epoch + 1, + ticks_per_epoch + ticks_per_slot, + ticks_per_epoch + ticks_per_slot + 1, + ticks_per_epoch + ticks_per_epoch - 2, + // ticks_per_epoch + ticks_per_epoch, ] { info!("Checking tick_height {}", *tick_height); leader_scheduler.update_tick_height(*tick_height, &bank); @@ -1134,6 +1139,7 @@ pub mod tests { leader_scheduler.get_leader_for_slot(1), Some(genesis_block.bootstrap_leader_id) ); + // The schedule for epoch 1 is known assert_eq!( leader_scheduler.get_leader_for_slot(2), @@ -1144,46 +1150,34 @@ pub mod tests { Some(genesis_block.bootstrap_leader_id) ); // The schedule for epoch 2 is unknown - assert_eq!(leader_scheduler.get_leader_for_slot(4), None); - } - - // - // Check various tick heights in epoch 1, and tick 0 of epoch 2 - // - for tick_height in &[ - ticks_per_epoch + 1, - ticks_per_epoch + ticks_per_slot, - ticks_per_epoch + ticks_per_slot + 1, - ticks_per_epoch + ticks_per_epoch - 1, - ticks_per_epoch + ticks_per_epoch, - ] { - info!("Checking tick_height {}", *tick_height); - leader_scheduler.update_tick_height(*tick_height, &bank); - assert_eq!(leader_scheduler.current_epoch, 2); - // The schedule for epoch 0 is unknown - assert_eq!(leader_scheduler.get_leader_for_slot(0), None); - assert_eq!(leader_scheduler.get_leader_for_slot(1), None); - // The schedule for epoch 1 is known - assert_eq!( - leader_scheduler.get_leader_for_slot(2), - Some(genesis_block.bootstrap_leader_id) - ); - assert_eq!( - leader_scheduler.get_leader_for_slot(3), - Some(genesis_block.bootstrap_leader_id) - ); - // The schedule for epoch 2 is known - assert_eq!( - leader_scheduler.get_leader_for_slot(4), - Some(genesis_block.bootstrap_leader_id) - ); - assert_eq!( - leader_scheduler.get_leader_for_slot(5), - Some(genesis_block.bootstrap_leader_id) - ); - // The schedule for epoch 3 is unknown assert_eq!(leader_scheduler.get_leader_for_slot(6), None); } + + leader_scheduler.update_tick_height(ticks_per_epoch + ticks_per_epoch - 1, &bank); + assert_eq!(leader_scheduler.current_epoch, 2); + // The schedule for epoch 0 is unknown + assert_eq!(leader_scheduler.get_leader_for_slot(0), None); + assert_eq!(leader_scheduler.get_leader_for_slot(1), None); + // The schedule for epoch 1 is known + assert_eq!( + leader_scheduler.get_leader_for_slot(2), + Some(genesis_block.bootstrap_leader_id) + ); + assert_eq!( + leader_scheduler.get_leader_for_slot(3), + Some(genesis_block.bootstrap_leader_id) + ); + // The schedule for epoch 2 is known + assert_eq!( + leader_scheduler.get_leader_for_slot(4), + Some(genesis_block.bootstrap_leader_id) + ); + assert_eq!( + leader_scheduler.get_leader_for_slot(5), + Some(genesis_block.bootstrap_leader_id) + ); + // The schedule for epoch 3 is unknown + assert_eq!(leader_scheduler.get_leader_for_slot(6), None); } #[test] @@ -1299,7 +1293,7 @@ pub mod tests { // Make sure the validator, not the leader is selected on the first slot of the // next epoch leader_scheduler.generate_schedule(1, &bank); - assert_eq!(leader_scheduler.current_epoch, 1); + assert_eq!(leader_scheduler.current_epoch, 0); if add_validator { assert_eq!(leader_scheduler.epoch_schedule[0][0], validator_id); } else { diff --git a/tests/multinode.rs b/tests/multinode.rs index 62b96f1dca..5ab4f4bca1 100644 --- a/tests/multinode.rs +++ b/tests/multinode.rs @@ -911,16 +911,10 @@ fn test_leader_to_validator_transition() { let (rotation_sender, rotation_receiver) = channel(); let leader_exit = leader.run(Some(rotation_sender)); - // There will be two rotations: - // slot 0 -> slot 1: bootstrap leader remains the leader - // slot 1 -> slot 2: bootstrap leader to the validator - let expected_rotations = vec![ - (FullnodeReturnType::LeaderToLeaderRotation, ticks_per_slot), - ( - FullnodeReturnType::LeaderToValidatorRotation, - 2 * ticks_per_slot, - ), - ]; + let expected_rotations = vec![( + FullnodeReturnType::LeaderToValidatorRotation, + ticks_per_slot, + )]; for expected_rotation in expected_rotations { loop { @@ -944,7 +938,7 @@ fn test_leader_to_validator_transition() { assert_eq!( bank.tick_height(), - 2 * fullnode_config.leader_scheduler_config.ticks_per_slot - 1 + fullnode_config.leader_scheduler_config.ticks_per_slot - 1 ); remove_dir_all(leader_ledger_path).unwrap(); }