Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ledger/src/leader_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
rand::distributions::{Distribution, WeightedIndex},
rand_chacha::{rand_core::SeedableRng, ChaChaRng},
solana_clock::Epoch,
solana_pubkey::Pubkey,
solana_pubkey::{Pubkey, PubkeyHasherBuilder},
std::{collections::HashMap, convert::identity, ops::Index, sync::Arc},
};

Expand All @@ -26,7 +26,7 @@ pub trait LeaderScheduleVariant:
std::fmt::Debug + Send + Sync + Index<u64, Output = Pubkey>
{
fn get_slot_leaders(&self) -> &[Pubkey];
fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Arc<Vec<usize>>>;
fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Vec<usize>, PubkeyHasherBuilder>;

/// Get the vote account address for the given epoch slot index. This is
/// guaranteed to be Some if the leader schedule is keyed by vote account
Expand Down
50 changes: 33 additions & 17 deletions ledger/src/leader_schedule/identity_keyed.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
#[cfg(feature = "dev-context-only-utils")]
use qualifier_attr::qualifiers;
use {
super::{stake_weighted_slot_leaders, LeaderScheduleVariant},
itertools::Itertools,
solana_clock::Epoch,
solana_pubkey::Pubkey,
std::{collections::HashMap, ops::Index, sync::Arc},
solana_pubkey::{Pubkey, PubkeyHasherBuilder},
std::{collections::HashMap, ops::Index},
};

#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct LeaderSchedule {
slot_leaders: Vec<Pubkey>,
// Inverted index from pubkeys to indices where they are the leader.
leader_slots_map: HashMap<Pubkey, Arc<Vec<usize>>>,
leader_slots_map: HashMap<Pubkey, Vec<usize>, PubkeyHasherBuilder>,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

We might need to change this fn to not cloning the vec anymore since it is no longer an Arc.

Copy link
Member Author

@vadorovsky vadorovsky Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this!

I tried to remove this cloned() and make it work with Vec<usize> (without Arc), but the problem is that the returned iterator is used outside of the LeaderSchedule, so then compiler was rightfully complaining about lifetimes and returning references to local values.

So I decided to put the Arc back. Now I can see its purpose.

Copy link
Member Author

@vadorovsky vadorovsky Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, adding Arc back unfortunately increases the execution time from 5.2ms to 9.9ms. 🥲 But I have no better ideas and that's still better than cloning.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks reasonable to me.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find a way to remove Arc.

#8499

The Arc was needed because get_leader_upcoming_slots returned a boxed iterator requiring ownership of the Vec. By modifying get_leader_upcoming_slots to return a borrowed iterator (Box<dyn Iterator + '_>), and collecting the Arc references upfront in next_leader_slot, the schedules stay alive for the entire iterator chain, eliminating the need for Arc wrapping and avoiding intermediate Vec allocations.

}

impl LeaderSchedule {
Expand All @@ -26,25 +27,40 @@ impl LeaderSchedule {
.map(|(pubkey, stake)| (pubkey, *stake))
.collect();
let slot_leaders = stake_weighted_slot_leaders(keyed_stakes, epoch, len, repeat);
Self::new_from_schedule(slot_leaders)
Self {
leader_slots_map: Self::invert_slot_leaders(
&slot_leaders,
Some(epoch_staked_nodes.len()),
),
slot_leaders,
}
}

#[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
pub fn new_from_schedule(slot_leaders: Vec<Pubkey>) -> Self {
Self {
leader_slots_map: Self::invert_slot_leaders(&slot_leaders),
leader_slots_map: Self::invert_slot_leaders(&slot_leaders, None),
slot_leaders,
}
}

fn invert_slot_leaders(slot_leaders: &[Pubkey]) -> HashMap<Pubkey, Arc<Vec<usize>>> {
slot_leaders
.iter()
.enumerate()
.map(|(i, pk)| (*pk, i))
.into_group_map()
.into_iter()
.map(|(k, v)| (k, Arc::new(v)))
.collect()
fn invert_slot_leaders(
slot_leaders: &[Pubkey],
nodes_len: Option<usize>,
) -> HashMap<Pubkey, Vec<usize>, PubkeyHasherBuilder> {
let mut grouped_slot_leaders = match nodes_len {
Some(nodes_len) => {
HashMap::with_capacity_and_hasher(nodes_len, PubkeyHasherBuilder::default())
}
None => HashMap::with_hasher(PubkeyHasherBuilder::default()),
};
for (slot, leader) in slot_leaders.iter().enumerate() {
grouped_slot_leaders
.entry(*leader)
.and_modify(|slots: &mut Vec<usize>| slots.push(slot))
.or_insert(vec![slot]);
}
grouped_slot_leaders
}

pub fn get_slot_leaders(&self) -> &[Pubkey] {
Expand All @@ -57,7 +73,7 @@ impl LeaderScheduleVariant for LeaderSchedule {
&self.slot_leaders
}

fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Arc<Vec<usize>>> {
fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Vec<usize>, PubkeyHasherBuilder> {
&self.leader_slots_map
}
}
Expand Down Expand Up @@ -183,7 +199,7 @@ mod tests {
victor_pubkey,
];

let grouped_slot_leaders = LeaderSchedule::invert_slot_leaders(leaders);
let grouped_slot_leaders = LeaderSchedule::invert_slot_leaders(leaders, Some(4));
assert_eq!(
grouped_slot_leaders.get(&alice_pubkey).unwrap().as_slice(),
&[0, 2, 5],
Expand Down
6 changes: 3 additions & 3 deletions ledger/src/leader_schedule/vote_keyed.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use {
super::{stake_weighted_slot_leaders, IdentityKeyedLeaderSchedule, LeaderScheduleVariant},
solana_clock::Epoch,
solana_pubkey::Pubkey,
solana_pubkey::{Pubkey, PubkeyHasherBuilder},
solana_vote::vote_account::VoteAccountsHashMap,
std::{collections::HashMap, ops::Index, sync::Arc},
std::{collections::HashMap, ops::Index},
};

#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -80,7 +80,7 @@ impl LeaderScheduleVariant for LeaderSchedule {
self.identity_keyed_leader_schedule.get_slot_leaders()
}

fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Arc<Vec<usize>>> {
fn get_leader_slots_map(&self) -> &HashMap<Pubkey, Vec<usize>, PubkeyHasherBuilder> {
self.identity_keyed_leader_schedule.get_leader_slots_map()
}

Expand Down
Loading