From a77055ae4423f6e1761f7e79a935afda4dbb4651 Mon Sep 17 00:00:00 2001 From: vad Date: Thu, 15 Jan 2026 14:12:39 +0100 Subject: [PATCH] ledger: Require leader schedules to be repeat-aligned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests were constructing schedules with `repeat > 1` but feeding arrays whose length wasn’t a multiple of `repeat`, so some leaders never repeated the expected number of times. That inconsistency also blocks follow-up work on optimizing the schedule calculation (see https://github.com/anza-xyz/agave/pull/9126). Add a debug assert that the schedule length is divisible by `repeat`, and fix the tests to generate properly aligned schedules. Change `TEST_SLOTS_PER_EPOCH` in rpc from 129 (an incorrect, unaligned value) to 256, which stays above the delinquent-slot threshold while keeping the tests fast. Ref: https://github.com/anza-xyz/agave/issues/8280 --- ledger/src/leader_schedule.rs | 5 ++++- ledger/src/leader_schedule/identity_keyed.rs | 2 +- ledger/src/leader_schedule/vote_keyed.rs | 2 +- rpc/src/rpc.rs | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ledger/src/leader_schedule.rs b/ledger/src/leader_schedule.rs index b7a51cbfd77..6cac75c3614 100644 --- a/ledger/src/leader_schedule.rs +++ b/ledger/src/leader_schedule.rs @@ -78,6 +78,10 @@ fn stake_weighted_slot_leaders( len: u64, repeat: u64, ) -> Vec { + debug_assert!( + len.is_multiple_of(repeat), + "expected `len` {len} to be divisible by `repeat` {repeat}" + ); sort_stakes(&mut keyed_stakes); let (keys, stakes): (Vec<_>, Vec<_>) = keyed_stakes.into_iter().unzip(); let weighted_index = WeightedU64Index::new(stakes).unwrap(); @@ -192,7 +196,6 @@ mod tests { #[test_case(457470, &[10, 20, 30], 12, 1, &[2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 2])] #[test_case(3466545, &[10, 20, 30], 12, 1, &[2, 2, 0, 0, 2, 1, 1, 1, 0, 0, 2, 2])] #[test_case(3466545, &[10, 20, 30], 13, 1, &[2, 2, 0, 0, 2, 1, 1, 1, 0, 0, 2, 2, 1])] - #[test_case(3466545, &[10, 20, 30], 13, 2, &[2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 1, 1, 1])] #[test_case(3466545, &[10, 20, 30], 14, 1, &[2, 2, 0, 0, 2, 1, 1, 1, 0, 0, 2, 2, 1, 2])] #[test_case(3466545, &[10, 20, 30], 14, 2, &[2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 1, 1, 1, 1])] fn test_stake_leader_schedule_exact_order( diff --git a/ledger/src/leader_schedule/identity_keyed.rs b/ledger/src/leader_schedule/identity_keyed.rs index 5def3702f21..afdcbf9ad3c 100644 --- a/ledger/src/leader_schedule/identity_keyed.rs +++ b/ledger/src/leader_schedule/identity_keyed.rs @@ -104,8 +104,8 @@ mod tests { .collect(); let epoch = rand::random::(); - let len = num_keys * 10; let repeat = 8; + let len = num_keys * repeat; let leader_schedule = LeaderSchedule::new(&stakes, epoch, len, repeat); assert_eq!(leader_schedule.num_slots() as u64, len); let mut leader_node = Pubkey::default(); diff --git a/ledger/src/leader_schedule/vote_keyed.rs b/ledger/src/leader_schedule/vote_keyed.rs index c3bc7a1164f..b8a28d34819 100644 --- a/ledger/src/leader_schedule/vote_keyed.rs +++ b/ledger/src/leader_schedule/vote_keyed.rs @@ -181,8 +181,8 @@ mod tests { .collect(); let epoch = rand::random::(); - let len = num_keys * 10; let repeat = 8; + let len = num_keys * repeat; let leader_schedule = LeaderSchedule::new(&vote_accounts_map, epoch, len, repeat); assert_eq!(leader_schedule.num_slots() as u64, len); let mut leader_node = Pubkey::default(); diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 6b3b55c9ead..b9b88981db8 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -4608,7 +4608,7 @@ pub mod tests { const TEST_MINT_LAMPORTS: u64 = 1_000_000_000; const TEST_SIGNATURE_FEE: u64 = 5_000; - const TEST_SLOTS_PER_EPOCH: u64 = DELINQUENT_VALIDATOR_SLOT_DISTANCE + 1; + const TEST_SLOTS_PER_EPOCH: u64 = 256; pub(crate) fn new_test_cluster_info() -> ClusterInfo { let keypair = Arc::new(Keypair::new()); @@ -5483,7 +5483,7 @@ pub mod tests { parse_success_result(rpc.handle_request_sync(request)); let expected = Some(HashMap::from_iter(std::iter::once(( rpc.leader_pubkey().to_string(), - Vec::from_iter(0..=128), + Vec::from_iter(0..TEST_SLOTS_PER_EPOCH as usize), )))); assert_eq!(result, expected); }