Hoist new leader scheduler up to protocol level
Attempt to feel similar to LeaderScheduler to easy migration.
This commit is contained in:
@ -15,7 +15,6 @@ fnv = "1.0.6"
|
||||
hashbrown = "0.1.8"
|
||||
log = "0.4.2"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
serde = "1.0.88"
|
||||
serde_derive = "1.0.88"
|
||||
serde_json = "1.0.38"
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
use crate::accounts::{Accounts, ErrorCounters, InstructionAccounts, InstructionLoaders};
|
||||
use crate::last_id_queue::LastIdQueue;
|
||||
use crate::leader_schedule::LeaderSchedule;
|
||||
use crate::runtime::{self, RuntimeError};
|
||||
use crate::status_cache::StatusCache;
|
||||
use bincode::serialize;
|
||||
@ -723,7 +722,9 @@ impl Bank {
|
||||
}
|
||||
|
||||
/// Return the checkpointed stakes that should be used to generate a leader schedule.
|
||||
fn staked_nodes_at_slot(&self, slot_height: u64) -> HashMap<Pubkey, u64> {
|
||||
fn staked_nodes_at_slot(&self, current_slot_height: u64) -> HashMap<Pubkey, u64> {
|
||||
let slot_height = current_slot_height.saturating_sub(self.stakers_slot_offset);
|
||||
|
||||
let parents = self.parents();
|
||||
let mut banks = vec![self];
|
||||
banks.extend(parents.iter().map(|x| x.as_ref()));
|
||||
@ -741,87 +742,6 @@ impl Bank {
|
||||
self.ticks_per_slot
|
||||
}
|
||||
|
||||
/// Return the number of slots per tick that should be used calls to epoch_height().
|
||||
pub fn slots_per_epoch(&self) -> u64 {
|
||||
self.slots_per_epoch
|
||||
}
|
||||
|
||||
/// Return the checkpointed stakes that should be used to generate a leader schedule.
|
||||
fn staked_nodes_at_epoch(&self, epoch_height: u64) -> HashMap<Pubkey, u64> {
|
||||
let epoch_slot_height = epoch_height * self.slots_per_epoch();
|
||||
let slot_height = epoch_slot_height.saturating_sub(self.stakers_slot_offset);
|
||||
self.staked_nodes_at_slot(slot_height)
|
||||
}
|
||||
|
||||
/// Return the leader schedule for the given epoch.
|
||||
fn leader_schedule(&self, epoch_height: u64) -> LeaderSchedule {
|
||||
let stakes = self.staked_nodes_at_epoch(epoch_height);
|
||||
let mut seed = [0u8; 32];
|
||||
seed[0..8].copy_from_slice(&epoch_height.to_le_bytes());
|
||||
let stakes: Vec<_> = stakes.into_iter().collect();
|
||||
LeaderSchedule::new(&stakes, seed, self.slots_per_epoch())
|
||||
}
|
||||
|
||||
/// Return the leader for the slot at the slot_index and epoch_height returned
|
||||
/// by the given function.
|
||||
pub fn slot_leader_by<F>(&self, get_slot_index: F) -> Pubkey
|
||||
where
|
||||
F: Fn(u64, u64, u64) -> (u64, u64),
|
||||
{
|
||||
let (slot_index, epoch_height) = get_slot_index(
|
||||
self.slot_index(),
|
||||
self.epoch_height(),
|
||||
self.slots_per_epoch(),
|
||||
);
|
||||
let leader_schedule = self.leader_schedule(epoch_height);
|
||||
leader_schedule[slot_index as usize]
|
||||
}
|
||||
|
||||
/// Return the leader for the current slot.
|
||||
pub fn slot_leader(&self) -> Pubkey {
|
||||
self.slot_leader_by(|slot_index, epoch_height, _| (slot_index, epoch_height))
|
||||
}
|
||||
|
||||
/// Return the epoch height and slot index of the slot before the current slot.
|
||||
fn prev_slot_leader_index(
|
||||
slot_index: u64,
|
||||
epoch_height: u64,
|
||||
slots_per_epoch: u64,
|
||||
) -> (u64, u64) {
|
||||
if epoch_height == 0 && slot_index == 0 {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
if slot_index == 0 {
|
||||
(slots_per_epoch - 1, epoch_height - 1)
|
||||
} else {
|
||||
(slot_index - 1, epoch_height)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the slot_index and epoch height of the slot following the current slot.
|
||||
fn next_slot_leader_index(
|
||||
slot_index: u64,
|
||||
epoch_height: u64,
|
||||
slots_per_epoch: u64,
|
||||
) -> (u64, u64) {
|
||||
if slot_index + 1 == slots_per_epoch {
|
||||
(0, epoch_height + 1)
|
||||
} else {
|
||||
(slot_index + 1, epoch_height)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the leader for the slot before the current slot.
|
||||
pub fn prev_slot_leader(&self) -> Pubkey {
|
||||
self.slot_leader_by(Self::prev_slot_leader_index)
|
||||
}
|
||||
|
||||
/// Return the leader for the slot following the current slot.
|
||||
pub fn next_slot_leader(&self) -> Pubkey {
|
||||
self.slot_leader_by(Self::next_slot_leader_index)
|
||||
}
|
||||
|
||||
/// Return the number of ticks since genesis.
|
||||
pub fn tick_height(&self) -> u64 {
|
||||
self.last_id_queue.read().unwrap().tick_height()
|
||||
@ -837,6 +757,17 @@ impl Bank {
|
||||
self.tick_height() / self.ticks_per_slot()
|
||||
}
|
||||
|
||||
/// Return the number of slots per tick.
|
||||
pub fn slots_per_epoch(&self) -> u64 {
|
||||
self.slots_per_epoch
|
||||
}
|
||||
|
||||
/// Return the checkpointed stakes that should be used to generate a leader schedule.
|
||||
pub fn staked_nodes_at_epoch(&self, epoch_height: u64) -> HashMap<Pubkey, u64> {
|
||||
let epoch_slot_height = epoch_height * self.slots_per_epoch();
|
||||
self.staked_nodes_at_slot(epoch_slot_height)
|
||||
}
|
||||
|
||||
/// Return the number of slots since the last epoch boundary.
|
||||
pub fn slot_index(&self) -> u64 {
|
||||
self.slot_height() % self.slots_per_epoch()
|
||||
@ -1262,23 +1193,7 @@ mod tests {
|
||||
let mut expected = HashMap::new();
|
||||
expected.insert(pubkey, bootstrap_tokens - 1);
|
||||
let bank = Bank::new_from_parent(&Arc::new(bank));
|
||||
assert_eq!(bank.staked_nodes_at_epoch(bank.epoch_height()), expected,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bank_leader_schedule_basic() {
|
||||
let pubkey = Keypair::new().pubkey();
|
||||
let (genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(2, pubkey, 2);
|
||||
let bank = Bank::new(&genesis_block);
|
||||
|
||||
let ids_and_stakes: Vec<_> = bank.staked_nodes().into_iter().collect();
|
||||
let mut seed = [0u8; 32];
|
||||
seed[0..8].copy_from_slice(&bank.epoch_height().to_le_bytes());
|
||||
let leader_schedule = LeaderSchedule::new(&ids_and_stakes, seed, bank.slots_per_epoch());
|
||||
|
||||
assert_eq!(leader_schedule[0], pubkey);
|
||||
assert_eq!(leader_schedule[1], pubkey);
|
||||
assert_eq!(leader_schedule[2], pubkey);
|
||||
assert_eq!(bank.staked_nodes_at_slot(bank.slot_height()), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1535,24 +1450,4 @@ mod tests {
|
||||
bank.squash();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bank_slot_leader_basic() {
|
||||
let pubkey = Keypair::new().pubkey();
|
||||
let bank = Bank::new(&GenesisBlock::new_with_leader(2, pubkey, 2).0);
|
||||
assert_eq!(bank.slot_leader(), pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bank_prev_slot_leader_index() {
|
||||
assert_eq!(Bank::prev_slot_leader_index(0, 0, 2), (0, 0));
|
||||
assert_eq!(Bank::prev_slot_leader_index(1, 0, 2), (0, 0));
|
||||
assert_eq!(Bank::prev_slot_leader_index(0, 1, 2), (1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bank_next_slot_leader_index() {
|
||||
assert_eq!(Bank::next_slot_leader_index(0, 0, 2), (1, 0));
|
||||
assert_eq!(Bank::next_slot_leader_index(1, 0, 2), (0, 1));
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
use rand::distributions::{Distribution, WeightedIndex};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::ops::Index;
|
||||
|
||||
/// Stake-weighted leader schedule for one epoch.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LeaderSchedule {
|
||||
slot_leaders: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
impl LeaderSchedule {
|
||||
// Note: passing in zero stakers will cause a panic.
|
||||
pub fn new(ids_and_stakes: &[(Pubkey, u64)], seed: [u8; 32], len: u64) -> Self {
|
||||
let (ids, stakes): (Vec<_>, Vec<_>) = ids_and_stakes.iter().cloned().unzip();
|
||||
let rng = &mut ChaChaRng::from_seed(seed);
|
||||
let weighted_index = WeightedIndex::new(stakes).unwrap();
|
||||
let slot_leaders = (0..len).map(|_| ids[weighted_index.sample(rng)]).collect();
|
||||
Self { slot_leaders }
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for LeaderSchedule {
|
||||
type Output = Pubkey;
|
||||
fn index(&self, index: usize) -> &Pubkey {
|
||||
&self.slot_leaders[index % self.slot_leaders.len()]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
|
||||
#[test]
|
||||
fn test_leader_schedule_index() {
|
||||
let pubkey0 = Keypair::new().pubkey();
|
||||
let pubkey1 = Keypair::new().pubkey();
|
||||
let leader_schedule = LeaderSchedule {
|
||||
slot_leaders: vec![pubkey0, pubkey1],
|
||||
};
|
||||
assert_eq!(leader_schedule[0], pubkey0);
|
||||
assert_eq!(leader_schedule[1], pubkey1);
|
||||
assert_eq!(leader_schedule[2], pubkey0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leader_schedule_basic() {
|
||||
let num_keys = 10;
|
||||
let stakes: Vec<_> = (0..num_keys)
|
||||
.map(|i| (Keypair::new().pubkey(), i))
|
||||
.collect();
|
||||
|
||||
let seed = Keypair::new().pubkey();
|
||||
let mut seed_bytes = [0u8; 32];
|
||||
seed_bytes.copy_from_slice(seed.as_ref());
|
||||
let len = num_keys * 10;
|
||||
let leader_schedule = LeaderSchedule::new(&stakes, seed_bytes, len);
|
||||
let leader_schedule2 = LeaderSchedule::new(&stakes, seed_bytes, len);
|
||||
assert_eq!(leader_schedule.slot_leaders.len() as u64, len);
|
||||
// Check that the same schedule is reproducibly generated
|
||||
assert_eq!(leader_schedule, leader_schedule2);
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ mod accounts;
|
||||
pub mod bank;
|
||||
pub mod bloom;
|
||||
mod last_id_queue;
|
||||
mod leader_schedule;
|
||||
mod runtime;
|
||||
mod status_cache;
|
||||
|
||||
|
Reference in New Issue
Block a user