Split ActiveStakers over Bank and LeaderScheduler
This commit is contained in:
parent
f89e83ae49
commit
a1070e9572
|
@ -663,6 +663,48 @@ impl Bank {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sort_stakes(stakes: &mut Vec<(Pubkey, u64)>) {
|
||||||
|
// Sort first by stake. If stakes are the same, sort by pubkey to ensure a
|
||||||
|
// deterministic result.
|
||||||
|
// Note: Use unstable sort, because we dedup right after to remove the equal elements.
|
||||||
|
stakes.sort_unstable_by(|(pubkey0, stake0), (pubkey1, stake1)| {
|
||||||
|
if stake0 == stake1 {
|
||||||
|
pubkey0.cmp(&pubkey1)
|
||||||
|
} else {
|
||||||
|
stake0.cmp(&stake1)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now that it's sorted, we can do an O(n) dedup.
|
||||||
|
stakes.dedup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a sorted, filtered list of staker/stake pairs.
|
||||||
|
pub fn filtered_stakes<F>(&self, filter: F) -> Vec<(Pubkey, u64)>
|
||||||
|
where
|
||||||
|
F: Fn(&VoteState) -> bool,
|
||||||
|
{
|
||||||
|
let mut stakes: Vec<_> = self
|
||||||
|
.vote_states(filter)
|
||||||
|
.iter()
|
||||||
|
.filter_map(|vote_state| {
|
||||||
|
let pubkey = vote_state.staker_id;
|
||||||
|
let stake = self.get_balance(&pubkey);
|
||||||
|
if stake > 0 {
|
||||||
|
Some((pubkey, stake))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self::sort_stakes(&mut stakes);
|
||||||
|
stakes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stakes(&self) -> Vec<(Pubkey, u64)> {
|
||||||
|
self.filtered_stakes(|_| true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of ticks per slot that should be used calls to slot_height().
|
/// Return the number of ticks per slot that should be used calls to slot_height().
|
||||||
pub fn ticks_per_slot(&self) -> u64 {
|
pub fn ticks_per_slot(&self) -> u64 {
|
||||||
self.ticks_per_slot
|
self.ticks_per_slot
|
||||||
|
@ -1366,4 +1408,30 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_stakes_basic() {
|
||||||
|
let pubkey0 = Keypair::new().pubkey();
|
||||||
|
let pubkey1 = Keypair::new().pubkey();
|
||||||
|
let mut stakes = vec![(pubkey0, 2), (pubkey1, 1)];
|
||||||
|
Bank::sort_stakes(&mut stakes);
|
||||||
|
assert_eq!(stakes, vec![(pubkey1, 1), (pubkey0, 2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_stakes_with_dup() {
|
||||||
|
let pubkey0 = Keypair::new().pubkey();
|
||||||
|
let pubkey1 = Keypair::new().pubkey();
|
||||||
|
let mut stakes = vec![(pubkey0, 1), (pubkey1, 2), (pubkey0, 1)];
|
||||||
|
Bank::sort_stakes(&mut stakes);
|
||||||
|
assert_eq!(stakes, vec![(pubkey0, 1), (pubkey1, 2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sort_stakes_with_equal_stakes() {
|
||||||
|
let pubkey0 = Pubkey::default();
|
||||||
|
let pubkey1 = Keypair::new().pubkey();
|
||||||
|
let mut stakes = vec![(pubkey0, 1), (pubkey1, 1)];
|
||||||
|
Bank::sort_stakes(&mut stakes);
|
||||||
|
assert_eq!(stakes, vec![(pubkey0, 1), (pubkey1, 1)]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,324 +0,0 @@
|
||||||
use solana_runtime::bank::Bank;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::timing::DEFAULT_SLOTS_PER_EPOCH;
|
|
||||||
use solana_sdk::vote_program::VoteState;
|
|
||||||
|
|
||||||
pub const DEFAULT_ACTIVE_WINDOW_NUM_SLOTS: u64 = DEFAULT_SLOTS_PER_EPOCH;
|
|
||||||
|
|
||||||
// Return true of the latest vote is between the lower and upper bounds (inclusive)
|
|
||||||
fn is_active_staker(vote_state: &VoteState, lower_bound: u64, upper_bound: u64) -> bool {
|
|
||||||
vote_state
|
|
||||||
.votes
|
|
||||||
.back()
|
|
||||||
.filter(|vote| vote.slot_height >= lower_bound && vote.slot_height <= upper_bound)
|
|
||||||
.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rank_stakes(stakes: &mut Vec<(Pubkey, u64)>) {
|
|
||||||
// Rank first by stake. If stakes are the same we rank by pubkey to ensure a
|
|
||||||
// deterministic result.
|
|
||||||
// Note: Use unstable sort, because we dedup right after to remove the equal elements.
|
|
||||||
stakes.sort_unstable_by(|(pubkey0, stake0), (pubkey1, stake1)| {
|
|
||||||
if stake0 == stake1 {
|
|
||||||
pubkey0.cmp(&pubkey1)
|
|
||||||
} else {
|
|
||||||
stake0.cmp(&stake1)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now that it's sorted, we can do an O(n) dedup.
|
|
||||||
stakes.dedup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The set of stakers that have voted near the time of construction
|
|
||||||
pub struct ActiveStakers {
|
|
||||||
stakes: Vec<(Pubkey, u64)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveStakers {
|
|
||||||
pub fn new_with_bounds(bank: &Bank, active_window_num_slots: u64, upper_bound: u64) -> Self {
|
|
||||||
let lower_bound = upper_bound.saturating_sub(active_window_num_slots);
|
|
||||||
let mut stakes: Vec<_> = bank
|
|
||||||
.vote_states(|vote_state| is_active_staker(vote_state, lower_bound, upper_bound))
|
|
||||||
.iter()
|
|
||||||
.filter_map(|vote_state| {
|
|
||||||
let pubkey = vote_state.staker_id;
|
|
||||||
let stake = bank.get_balance(&pubkey);
|
|
||||||
if stake > 0 {
|
|
||||||
Some((pubkey, stake))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
rank_stakes(&mut stakes);
|
|
||||||
Self { stakes }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(bank: &Bank) -> Self {
|
|
||||||
Self::new_with_bounds(bank, DEFAULT_ACTIVE_WINDOW_NUM_SLOTS, bank.slot_height())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sorted_stakes(&self) -> Vec<(Pubkey, u64)> {
|
|
||||||
self.stakes.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the pubkeys of each staker.
|
|
||||||
pub fn pubkeys(&self) -> Vec<Pubkey> {
|
|
||||||
self.stakes.iter().map(|(pubkey, _stake)| *pubkey).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the sorted pubkeys of each staker. Useful for testing.
|
|
||||||
pub fn sorted_pubkeys(&self) -> Vec<Pubkey> {
|
|
||||||
let mut pubkeys = self.pubkeys();
|
|
||||||
pubkeys.sort_unstable();
|
|
||||||
pubkeys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod tests {
|
|
||||||
use super::*;
|
|
||||||
use solana_runtime::bank::Bank;
|
|
||||||
use solana_sdk::genesis_block::GenesisBlock;
|
|
||||||
use solana_sdk::pubkey::Pubkey;
|
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
|
||||||
use solana_sdk::vote_transaction::VoteTransaction;
|
|
||||||
|
|
||||||
pub fn new_vote_account(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
voting_pubkey: &Pubkey,
|
|
||||||
bank: &Bank,
|
|
||||||
num_tokens: u64,
|
|
||||||
) {
|
|
||||||
let last_id = bank.last_id();
|
|
||||||
let tx = VoteTransaction::new_account(from_keypair, *voting_pubkey, last_id, num_tokens, 0);
|
|
||||||
bank.process_transaction(&tx).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot_height: u64) {
|
|
||||||
let last_id = bank.last_id();
|
|
||||||
let tx = VoteTransaction::new_vote(voting_keypair, slot_height, last_id, 0);
|
|
||||||
bank.process_transaction(&tx).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_vote_account_with_vote<T: KeypairUtil>(
|
|
||||||
from_keypair: &Keypair,
|
|
||||||
voting_keypair: &T,
|
|
||||||
bank: &Bank,
|
|
||||||
num_tokens: u64,
|
|
||||||
slot_height: u64,
|
|
||||||
) {
|
|
||||||
new_vote_account(from_keypair, &voting_keypair.pubkey(), bank, num_tokens);
|
|
||||||
push_vote(voting_keypair, bank, slot_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_active_set() {
|
|
||||||
solana_logger::setup();
|
|
||||||
|
|
||||||
let leader_id = Keypair::new().pubkey();
|
|
||||||
let active_window_tick_length = 1000;
|
|
||||||
let (genesis_block, mint_keypair) = GenesisBlock::new_with_leader(10000, leader_id, 500);
|
|
||||||
let bank = Bank::new(&genesis_block);
|
|
||||||
|
|
||||||
let bootstrap_ids = vec![genesis_block.bootstrap_leader_id];
|
|
||||||
|
|
||||||
// Insert a bunch of votes at height "start_height"
|
|
||||||
let start_height = 3;
|
|
||||||
let num_old_ids = 20;
|
|
||||||
let mut old_ids = vec![];
|
|
||||||
for _ in 0..num_old_ids {
|
|
||||||
let new_keypair = Keypair::new();
|
|
||||||
let pk = new_keypair.pubkey();
|
|
||||||
old_ids.push(pk);
|
|
||||||
|
|
||||||
// Give the account some stake
|
|
||||||
bank.transfer(5, &mint_keypair, pk, genesis_block.last_id())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create a vote account and push a vote
|
|
||||||
new_vote_account_with_vote(&new_keypair, &Keypair::new(), &bank, 1, start_height);
|
|
||||||
}
|
|
||||||
old_ids.sort();
|
|
||||||
|
|
||||||
// Insert a bunch of votes at height "start_height + active_window_tick_length"
|
|
||||||
let num_new_ids = 10;
|
|
||||||
let mut new_ids = vec![];
|
|
||||||
for _ in 0..num_new_ids {
|
|
||||||
let new_keypair = Keypair::new();
|
|
||||||
let pk = new_keypair.pubkey();
|
|
||||||
new_ids.push(pk);
|
|
||||||
// Give the account some stake
|
|
||||||
bank.transfer(5, &mint_keypair, pk, genesis_block.last_id())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create a vote account and push a vote
|
|
||||||
let slot_height = start_height + active_window_tick_length + 1;
|
|
||||||
new_vote_account_with_vote(&new_keypair, &Keypair::new(), &bank, 1, slot_height);
|
|
||||||
}
|
|
||||||
new_ids.sort();
|
|
||||||
|
|
||||||
// Query for the active set at various heights
|
|
||||||
let result = ActiveStakers::new_with_bounds(&bank, active_window_tick_length, 0).pubkeys();
|
|
||||||
assert_eq!(result, bootstrap_ids);
|
|
||||||
|
|
||||||
let result =
|
|
||||||
ActiveStakers::new_with_bounds(&bank, active_window_tick_length, start_height - 1)
|
|
||||||
.pubkeys();
|
|
||||||
assert_eq!(result, bootstrap_ids);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + start_height - 1,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, old_ids);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + start_height,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, old_ids);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + start_height + 1,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, new_ids);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
2 * active_window_tick_length + start_height,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, new_ids);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
2 * active_window_tick_length + start_height + 1,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, new_ids);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
2 * active_window_tick_length + start_height + 2,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiple_vote() {
|
|
||||||
let leader_keypair = Keypair::new();
|
|
||||||
let leader_id = leader_keypair.pubkey();
|
|
||||||
let active_window_tick_length = 1000;
|
|
||||||
let (genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(10000, leader_id, 500);
|
|
||||||
let bank = Bank::new(&genesis_block);
|
|
||||||
|
|
||||||
// Bootstrap leader should be in the active set even without explicit votes
|
|
||||||
{
|
|
||||||
let result = ActiveStakers::new_with_bounds(&bank, active_window_tick_length, 0)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, vec![leader_id]);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, vec![leader_id]);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + 1,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that a node that votes twice in a row will get included in the active
|
|
||||||
// window
|
|
||||||
|
|
||||||
// Create a vote account
|
|
||||||
let voting_keypair = Keypair::new();
|
|
||||||
new_vote_account_with_vote(&leader_keypair, &voting_keypair, &bank, 1, 1);
|
|
||||||
|
|
||||||
{
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + 1,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, vec![leader_id]);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + 2,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vote at slot_height 2
|
|
||||||
push_vote(&voting_keypair, &bank, 2);
|
|
||||||
|
|
||||||
{
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + 2,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result, vec![leader_id]);
|
|
||||||
|
|
||||||
let result = ActiveStakers::new_with_bounds(
|
|
||||||
&bank,
|
|
||||||
active_window_tick_length,
|
|
||||||
active_window_tick_length + 3,
|
|
||||||
)
|
|
||||||
.sorted_pubkeys();
|
|
||||||
assert_eq!(result.len(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rank_stakes_basic() {
|
|
||||||
let pubkey0 = Keypair::new().pubkey();
|
|
||||||
let pubkey1 = Keypair::new().pubkey();
|
|
||||||
let mut stakes = vec![(pubkey0, 2), (pubkey1, 1)];
|
|
||||||
rank_stakes(&mut stakes);
|
|
||||||
assert_eq!(stakes, vec![(pubkey1, 1), (pubkey0, 2)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rank_stakes_with_dup() {
|
|
||||||
let pubkey0 = Keypair::new().pubkey();
|
|
||||||
let pubkey1 = Keypair::new().pubkey();
|
|
||||||
let mut stakes = vec![(pubkey0, 1), (pubkey1, 2), (pubkey0, 1)];
|
|
||||||
rank_stakes(&mut stakes);
|
|
||||||
assert_eq!(stakes, vec![(pubkey0, 1), (pubkey1, 2)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rank_stakes_with_equal_stakes() {
|
|
||||||
let pubkey0 = Pubkey::default();
|
|
||||||
let pubkey1 = Keypair::new().pubkey();
|
|
||||||
let mut stakes = vec![(pubkey0, 1), (pubkey1, 1)];
|
|
||||||
rank_stakes(&mut stakes);
|
|
||||||
assert_eq!(stakes, vec![(pubkey0, 1), (pubkey1, 1)]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -137,9 +137,9 @@ impl Service for LeaderConfirmationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::active_stakers::tests::{new_vote_account, push_vote};
|
use crate::voting_keypair::tests::{new_vote_account, push_vote};
|
||||||
use crate::voting_keypair::VotingKeypair;
|
use crate::voting_keypair::VotingKeypair;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
use solana_sdk::genesis_block::GenesisBlock;
|
use solana_sdk::genesis_block::GenesisBlock;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::active_stakers::ActiveStakers;
|
|
||||||
use rand::distributions::{Distribution, WeightedIndex};
|
use rand::distributions::{Distribution, WeightedIndex};
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
|
@ -30,14 +29,9 @@ impl LeaderSchedule {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_bank(bank: &Bank) -> Self {
|
pub fn new_with_bank(bank: &Bank) -> Self {
|
||||||
let active_stakers = ActiveStakers::new(&bank);
|
|
||||||
let mut seed = [0u8; 32];
|
let mut seed = [0u8; 32];
|
||||||
seed.copy_from_slice(bank.last_id().as_ref());
|
seed.copy_from_slice(bank.last_id().as_ref());
|
||||||
Self::new(
|
Self::new(&bank.stakes(), &seed, bank.slots_per_epoch())
|
||||||
&active_stakers.sorted_stakes(),
|
|
||||||
&seed,
|
|
||||||
bank.slots_per_epoch(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! The `leader_scheduler` module implements a structure and functions for tracking and
|
//! The `leader_scheduler` module implements a structure and functions for tracking and
|
||||||
//! managing the schedule for leader rotation
|
//! managing the schedule for leader rotation
|
||||||
|
|
||||||
use crate::active_stakers::{ActiveStakers, DEFAULT_ACTIVE_WINDOW_NUM_SLOTS};
|
|
||||||
use crate::entry::{create_ticks, next_entry_mut, Entry};
|
use crate::entry::{create_ticks, next_entry_mut, Entry};
|
||||||
use crate::voting_keypair::VotingKeypair;
|
use crate::voting_keypair::VotingKeypair;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
|
@ -12,10 +11,13 @@ use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
use solana_sdk::system_transaction::SystemTransaction;
|
use solana_sdk::system_transaction::SystemTransaction;
|
||||||
use solana_sdk::timing::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT};
|
use solana_sdk::timing::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT};
|
||||||
|
use solana_sdk::vote_program::VoteState;
|
||||||
use solana_sdk::vote_transaction::VoteTransaction;
|
use solana_sdk::vote_transaction::VoteTransaction;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub const DEFAULT_ACTIVE_WINDOW_NUM_SLOTS: u64 = DEFAULT_SLOTS_PER_EPOCH;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LeaderSchedulerConfig {
|
pub struct LeaderSchedulerConfig {
|
||||||
pub ticks_per_slot: u64,
|
pub ticks_per_slot: u64,
|
||||||
|
@ -47,6 +49,24 @@ impl Default for LeaderSchedulerConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return true of the latest vote is between the lower and upper bounds (inclusive)
|
||||||
|
fn is_active_staker(vote_state: &VoteState, lower_bound: u64, upper_bound: u64) -> bool {
|
||||||
|
vote_state
|
||||||
|
.votes
|
||||||
|
.back()
|
||||||
|
.filter(|vote| vote.slot_height >= lower_bound && vote.slot_height <= upper_bound)
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_active_stakes(
|
||||||
|
bank: &Bank,
|
||||||
|
active_window_num_slots: u64,
|
||||||
|
upper_bound: u64,
|
||||||
|
) -> Vec<(Pubkey, u64)> {
|
||||||
|
let lower_bound = upper_bound.saturating_sub(active_window_num_slots);
|
||||||
|
bank.filtered_stakes(|vote_state| is_active_staker(vote_state, lower_bound, upper_bound))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LeaderScheduler {
|
pub struct LeaderScheduler {
|
||||||
// A leader slot duration in ticks
|
// A leader slot duration in ticks
|
||||||
|
@ -242,9 +262,7 @@ impl LeaderScheduler {
|
||||||
|
|
||||||
self.seed = Self::calculate_seed(tick_height);
|
self.seed = Self::calculate_seed(tick_height);
|
||||||
let slot = self.tick_height_to_slot(tick_height);
|
let slot = self.tick_height_to_slot(tick_height);
|
||||||
let ranked_active_set =
|
let ranked_active_set = get_active_stakes(&bank, self.active_window_num_slots, slot);
|
||||||
ActiveStakers::new_with_bounds(&bank, self.active_window_num_slots, slot)
|
|
||||||
.sorted_stakes();
|
|
||||||
|
|
||||||
if ranked_active_set.is_empty() {
|
if ranked_active_set.is_empty() {
|
||||||
info!(
|
info!(
|
||||||
|
@ -398,9 +416,186 @@ pub fn make_active_set_entries(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::active_stakers::tests::new_vote_account_with_vote;
|
use crate::voting_keypair::tests::{new_vote_account_with_vote, push_vote};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
use solana_runtime::bank::Bank;
|
||||||
use solana_sdk::genesis_block::{GenesisBlock, BOOTSTRAP_LEADER_TOKENS};
|
use solana_sdk::genesis_block::{GenesisBlock, BOOTSTRAP_LEADER_TOKENS};
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_sdk::timing::DEFAULT_SLOTS_PER_EPOCH;
|
||||||
|
|
||||||
|
fn get_active_pubkeys(
|
||||||
|
bank: &Bank,
|
||||||
|
active_window_num_slots: u64,
|
||||||
|
upper_bound: u64,
|
||||||
|
) -> Vec<Pubkey> {
|
||||||
|
let stakes = get_active_stakes(bank, active_window_num_slots, upper_bound);
|
||||||
|
stakes.into_iter().map(|x| x.0).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_active_set() {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
let leader_id = Keypair::new().pubkey();
|
||||||
|
let active_window_tick_length = 1000;
|
||||||
|
let (genesis_block, mint_keypair) = GenesisBlock::new_with_leader(10000, leader_id, 500);
|
||||||
|
let bank = Bank::new(&genesis_block);
|
||||||
|
|
||||||
|
let bootstrap_ids = vec![genesis_block.bootstrap_leader_id];
|
||||||
|
|
||||||
|
// Insert a bunch of votes at height "start_height"
|
||||||
|
let start_height = 3;
|
||||||
|
let num_old_ids = 20;
|
||||||
|
let mut old_ids = vec![];
|
||||||
|
for _ in 0..num_old_ids {
|
||||||
|
let new_keypair = Keypair::new();
|
||||||
|
let pk = new_keypair.pubkey();
|
||||||
|
old_ids.push(pk);
|
||||||
|
|
||||||
|
// Give the account some stake
|
||||||
|
bank.transfer(5, &mint_keypair, pk, genesis_block.last_id())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Create a vote account and push a vote
|
||||||
|
new_vote_account_with_vote(&new_keypair, &Keypair::new(), &bank, 1, start_height);
|
||||||
|
}
|
||||||
|
old_ids.sort();
|
||||||
|
|
||||||
|
// Insert a bunch of votes at height "start_height + active_window_tick_length"
|
||||||
|
let num_new_ids = 10;
|
||||||
|
let mut new_ids = vec![];
|
||||||
|
for _ in 0..num_new_ids {
|
||||||
|
let new_keypair = Keypair::new();
|
||||||
|
let pk = new_keypair.pubkey();
|
||||||
|
new_ids.push(pk);
|
||||||
|
// Give the account some stake
|
||||||
|
bank.transfer(5, &mint_keypair, pk, genesis_block.last_id())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Create a vote account and push a vote
|
||||||
|
let slot_height = start_height + active_window_tick_length + 1;
|
||||||
|
new_vote_account_with_vote(&new_keypair, &Keypair::new(), &bank, 1, slot_height);
|
||||||
|
}
|
||||||
|
new_ids.sort();
|
||||||
|
|
||||||
|
// Query for the active set at various heights
|
||||||
|
let result = get_active_pubkeys(&bank, active_window_tick_length, 0);
|
||||||
|
assert_eq!(result, bootstrap_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(&bank, active_window_tick_length, start_height - 1);
|
||||||
|
assert_eq!(result, bootstrap_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + start_height - 1,
|
||||||
|
);
|
||||||
|
assert_eq!(result, old_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + start_height,
|
||||||
|
);
|
||||||
|
assert_eq!(result, old_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + start_height + 1,
|
||||||
|
);
|
||||||
|
assert_eq!(result, new_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
2 * active_window_tick_length + start_height,
|
||||||
|
);
|
||||||
|
assert_eq!(result, new_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
2 * active_window_tick_length + start_height + 1,
|
||||||
|
);
|
||||||
|
assert_eq!(result, new_ids);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
2 * active_window_tick_length + start_height + 2,
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_vote() {
|
||||||
|
let leader_keypair = Keypair::new();
|
||||||
|
let leader_id = leader_keypair.pubkey();
|
||||||
|
let active_window_tick_length = 1000;
|
||||||
|
let (genesis_block, _mint_keypair) = GenesisBlock::new_with_leader(10000, leader_id, 500);
|
||||||
|
let bank = Bank::new(&genesis_block);
|
||||||
|
|
||||||
|
// Bootstrap leader should be in the active set even without explicit votes
|
||||||
|
{
|
||||||
|
let result = get_active_pubkeys(&bank, active_window_tick_length, 0);
|
||||||
|
assert_eq!(result, vec![leader_id]);
|
||||||
|
|
||||||
|
let result =
|
||||||
|
get_active_pubkeys(&bank, active_window_tick_length, active_window_tick_length);
|
||||||
|
assert_eq!(result, vec![leader_id]);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + 1,
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a node that votes twice in a row will get included in the active
|
||||||
|
// window
|
||||||
|
|
||||||
|
// Create a vote account
|
||||||
|
let voting_keypair = Keypair::new();
|
||||||
|
new_vote_account_with_vote(&leader_keypair, &voting_keypair, &bank, 1, 1);
|
||||||
|
|
||||||
|
{
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + 1,
|
||||||
|
);
|
||||||
|
assert_eq!(result, vec![leader_id]);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + 2,
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote at slot_height 2
|
||||||
|
push_vote(&voting_keypair, &bank, 2);
|
||||||
|
|
||||||
|
{
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + 2,
|
||||||
|
);
|
||||||
|
assert_eq!(result, vec![leader_id]);
|
||||||
|
|
||||||
|
let result = get_active_pubkeys(
|
||||||
|
&bank,
|
||||||
|
active_window_tick_length,
|
||||||
|
active_window_tick_length + 3,
|
||||||
|
);
|
||||||
|
assert_eq!(result.len(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_scheduler_test(num_validators: u64, ticks_per_slot: u64, slots_per_epoch: u64) {
|
fn run_scheduler_test(num_validators: u64, ticks_per_slot: u64, slots_per_epoch: u64) {
|
||||||
info!(
|
info!(
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
//!
|
//!
|
||||||
|
|
||||||
#![cfg_attr(feature = "unstable", feature(test))]
|
#![cfg_attr(feature = "unstable", feature(test))]
|
||||||
pub mod active_stakers;
|
|
||||||
pub mod bank_forks;
|
pub mod bank_forks;
|
||||||
pub mod banking_stage;
|
pub mod banking_stage;
|
||||||
pub mod blob_fetch_stage;
|
pub mod blob_fetch_stage;
|
||||||
|
|
|
@ -95,3 +95,39 @@ impl VotingKeypair {
|
||||||
Self::new_with_signer(keypair, Box::new(LocalVoteSigner::default()))
|
Self::new_with_signer(keypair, Box::new(LocalVoteSigner::default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use solana_runtime::bank::Bank;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_sdk::vote_transaction::VoteTransaction;
|
||||||
|
|
||||||
|
pub fn new_vote_account(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
voting_pubkey: &Pubkey,
|
||||||
|
bank: &Bank,
|
||||||
|
num_tokens: u64,
|
||||||
|
) {
|
||||||
|
let last_id = bank.last_id();
|
||||||
|
let tx = VoteTransaction::new_account(from_keypair, *voting_pubkey, last_id, num_tokens, 0);
|
||||||
|
bank.process_transaction(&tx).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_vote<T: KeypairUtil>(voting_keypair: &T, bank: &Bank, slot_height: u64) {
|
||||||
|
let last_id = bank.last_id();
|
||||||
|
let tx = VoteTransaction::new_vote(voting_keypair, slot_height, last_id, 0);
|
||||||
|
bank.process_transaction(&tx).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_vote_account_with_vote<T: KeypairUtil>(
|
||||||
|
from_keypair: &Keypair,
|
||||||
|
voting_keypair: &T,
|
||||||
|
bank: &Bank,
|
||||||
|
num_tokens: u64,
|
||||||
|
slot_height: u64,
|
||||||
|
) {
|
||||||
|
new_vote_account(from_keypair, &voting_keypair.pubkey(), bank, num_tokens);
|
||||||
|
push_vote(voting_keypair, bank, slot_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue