add epoch_schedule sysvar (#6256)

* add epoch_schedule sysvar

* book sheesh!
This commit is contained in:
Rob Walker
2019-10-08 22:34:26 -07:00
committed by GitHub
parent f2ee01ace3
commit 7cf90766a3
46 changed files with 572 additions and 427 deletions

View File

@ -17,6 +17,7 @@ pub const DEFAULT_SLOTS_PER_SEGMENT: u64 = 1024;
// 4 times longer than the max_lockout to allow enough time for PoRep (128 slots)
pub const DEFAULT_SLOTS_PER_TURN: u64 = 32 * 4;
// leader schedule is governed by this
pub const NUM_CONSECUTIVE_LEADER_SLOTS: u64 = 4;
/// The time window of recent block hash values that the bank will track the signatures

198
sdk/src/epoch_schedule.rs Normal file
View File

@ -0,0 +1,198 @@
//! configuration for epochs, slots
/// 1 Epoch = 400 * 8192 ms ~= 55 minutes
pub use crate::clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH};
/// The number of slots before an epoch starts to calculate the leader schedule.
/// Default is an entire epoch, i.e. leader schedule for epoch X is calculated at
/// the beginning of epoch X - 1.
pub const DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET: u64 = DEFAULT_SLOTS_PER_EPOCH;
/// based on MAX_LOCKOUT_HISTORY from vote_api
pub const MINIMUM_SLOTS_PER_EPOCH: u64 = 32;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct EpochSchedule {
/// The maximum number of slots in each epoch.
pub slots_per_epoch: u64,
/// A number of slots before beginning of an epoch to calculate
/// a leader schedule for that epoch
pub leader_schedule_slot_offset: u64,
/// whether epochs start short and grow
pub warmup: bool,
/// basically: log2(slots_per_epoch) - log2(MINIMUM_SLOT_LEN)
pub first_normal_epoch: Epoch,
/// basically: 2.pow(first_normal_epoch) - MINIMUM_SLOT_LEN
pub first_normal_slot: Slot,
}
impl Default for EpochSchedule {
fn default() -> Self {
Self::custom(
DEFAULT_SLOTS_PER_EPOCH,
DEFAULT_LEADER_SCHEDULE_SLOT_OFFSET,
true,
)
}
}
impl EpochSchedule {
pub fn new(slots_per_epoch: u64) -> Self {
Self::custom(slots_per_epoch, slots_per_epoch, true)
}
pub fn custom(slots_per_epoch: u64, leader_schedule_slot_offset: u64, warmup: bool) -> Self {
assert!(slots_per_epoch >= MINIMUM_SLOTS_PER_EPOCH as u64);
let (first_normal_epoch, first_normal_slot) = if warmup {
let next_power_of_two = slots_per_epoch.next_power_of_two();
let log2_slots_per_epoch = next_power_of_two
.trailing_zeros()
.saturating_sub(MINIMUM_SLOTS_PER_EPOCH.trailing_zeros());
(
u64::from(log2_slots_per_epoch),
next_power_of_two.saturating_sub(MINIMUM_SLOTS_PER_EPOCH),
)
} else {
(0, 0)
};
EpochSchedule {
slots_per_epoch,
leader_schedule_slot_offset,
warmup,
first_normal_epoch,
first_normal_slot,
}
}
/// get the length of the given epoch (in slots)
pub fn get_slots_in_epoch(&self, epoch: u64) -> u64 {
if epoch < self.first_normal_epoch {
2u64.pow(epoch as u32 + MINIMUM_SLOTS_PER_EPOCH.trailing_zeros() as u32)
} else {
self.slots_per_epoch
}
}
/// get the epoch for which the given slot should save off
/// information about stakers
pub fn get_leader_schedule_epoch(&self, slot: u64) -> u64 {
if slot < self.first_normal_slot {
// until we get to normal slots, behave as if leader_schedule_slot_offset == slots_per_epoch
self.get_epoch_and_slot_index(slot).0 + 1
} else {
self.first_normal_epoch
+ (slot - self.first_normal_slot + self.leader_schedule_slot_offset)
/ self.slots_per_epoch
}
}
/// get epoch for the given slot
pub fn get_epoch(&self, slot: u64) -> u64 {
self.get_epoch_and_slot_index(slot).0
}
/// get epoch and offset into the epoch for the given slot
pub fn get_epoch_and_slot_index(&self, slot: u64) -> (u64, u64) {
if slot < self.first_normal_slot {
let epoch = (slot + MINIMUM_SLOTS_PER_EPOCH + 1)
.next_power_of_two()
.trailing_zeros()
- MINIMUM_SLOTS_PER_EPOCH.trailing_zeros()
- 1;
let epoch_len = 2u64.pow(epoch + MINIMUM_SLOTS_PER_EPOCH.trailing_zeros());
(
u64::from(epoch),
slot - (epoch_len - MINIMUM_SLOTS_PER_EPOCH),
)
} else {
(
self.first_normal_epoch + ((slot - self.first_normal_slot) / self.slots_per_epoch),
(slot - self.first_normal_slot) % self.slots_per_epoch,
)
}
}
pub fn get_first_slot_in_epoch(&self, epoch: u64) -> u64 {
if epoch <= self.first_normal_epoch {
(2u64.pow(epoch as u32) - 1) * MINIMUM_SLOTS_PER_EPOCH
} else {
(epoch - self.first_normal_epoch) * self.slots_per_epoch + self.first_normal_slot
}
}
pub fn get_last_slot_in_epoch(&self, epoch: u64) -> u64 {
self.get_first_slot_in_epoch(epoch) + self.get_slots_in_epoch(epoch) - 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_epoch_schedule() {
// one week of slots at 8 ticks/slot, 10 ticks/sec is
// (1 * 7 * 24 * 4500u64).next_power_of_two();
// test values between MINIMUM_SLOT_LEN and MINIMUM_SLOT_LEN * 16, should cover a good mix
for slots_per_epoch in MINIMUM_SLOTS_PER_EPOCH..=MINIMUM_SLOTS_PER_EPOCH * 16 {
let epoch_schedule = EpochSchedule::custom(slots_per_epoch, slots_per_epoch / 2, true);
assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0);
assert_eq!(
epoch_schedule.get_last_slot_in_epoch(0),
MINIMUM_SLOTS_PER_EPOCH - 1
);
let mut last_leader_schedule = 0;
let mut last_epoch = 0;
let mut last_slots_in_epoch = MINIMUM_SLOTS_PER_EPOCH;
for slot in 0..(2 * slots_per_epoch) {
// verify that leader_schedule_epoch is continuous over the warmup
// and into the first normal epoch
let leader_schedule = epoch_schedule.get_leader_schedule_epoch(slot);
if leader_schedule != last_leader_schedule {
assert_eq!(leader_schedule, last_leader_schedule + 1);
last_leader_schedule = leader_schedule;
}
let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot);
// verify that epoch increases continuously
if epoch != last_epoch {
assert_eq!(epoch, last_epoch + 1);
last_epoch = epoch;
assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot);
assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1);
// verify that slots in an epoch double continuously
// until they reach slots_per_epoch
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
if slots_in_epoch != last_slots_in_epoch {
if slots_in_epoch != slots_per_epoch {
assert_eq!(slots_in_epoch, last_slots_in_epoch * 2);
}
}
last_slots_in_epoch = slots_in_epoch;
}
// verify that the slot offset is less than slots_in_epoch
assert!(offset < last_slots_in_epoch);
}
// assert that these changed ;)
assert!(last_leader_schedule != 0); // t
assert!(last_epoch != 0);
// assert that we got to "normal" mode
assert!(last_slots_in_epoch == slots_per_epoch);
}
}
}

View File

@ -45,11 +45,11 @@ impl Default for FeeCalculator {
}
impl FeeCalculator {
pub fn new(target_lamports_per_signature: u64) -> Self {
pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: usize) -> Self {
let base_fee_calculator = Self {
target_lamports_per_signature,
lamports_per_signature: target_lamports_per_signature,
target_signatures_per_slot: 0,
target_signatures_per_slot,
..FeeCalculator::default()
};
@ -160,20 +160,20 @@ mod tests {
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
// No signature, no fee.
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
assert_eq!(FeeCalculator::new(1, 0).calculate_fee(&message), 0);
// One signature, a fee.
let pubkey0 = Pubkey::new(&[0; 32]);
let pubkey1 = Pubkey::new(&[1; 32]);
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let message = Message::new(vec![ix0]);
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
assert_eq!(FeeCalculator::new(2, 0).calculate_fee(&message), 2);
// Two signatures, double the fee.
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
let message = Message::new(vec![ix0, ix1]);
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
assert_eq!(FeeCalculator::new(2, 0).calculate_fee(&message), 4);
}
#[test]

View File

@ -1,35 +1,38 @@
//! The `genesis_block` module is a library for generating the chain's genesis block.
use crate::account::Account;
use crate::clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT};
use crate::fee_calculator::FeeCalculator;
use crate::hash::{hash, Hash};
use crate::inflation::Inflation;
use crate::poh_config::PohConfig;
use crate::pubkey::Pubkey;
use crate::rent_calculator::RentCalculator;
use crate::signature::{Keypair, KeypairUtil};
use crate::system_program::{self, solana_system_program};
use crate::{
account::Account,
clock::{DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT},
epoch_schedule::EpochSchedule,
fee_calculator::FeeCalculator,
hash::{hash, Hash},
inflation::Inflation,
poh_config::PohConfig,
pubkey::Pubkey,
rent_calculator::RentCalculator,
signature::{Keypair, KeypairUtil},
system_program::{self, solana_system_program},
};
use bincode::{deserialize, serialize};
use memmap::Mmap;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::{
fs::{File, OpenOptions},
io::Write,
path::Path,
};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GenesisBlock {
pub accounts: Vec<(Pubkey, Account)>,
pub native_instruction_processors: Vec<(String, Pubkey)>,
pub rewards_pools: Vec<(Pubkey, Account)>,
pub slots_per_epoch: u64,
pub stakers_slot_offset: u64,
pub epoch_warmup: bool,
pub ticks_per_slot: u64,
pub slots_per_segment: u64,
pub poh_config: PohConfig,
pub fee_calculator: FeeCalculator,
pub rent_calculator: RentCalculator,
pub inflation: Inflation,
pub epoch_schedule: EpochSchedule,
}
// useful for basic tests
@ -53,15 +56,13 @@ impl Default for GenesisBlock {
accounts: Vec::new(),
native_instruction_processors: Vec::new(),
rewards_pools: Vec::new(),
epoch_warmup: true,
slots_per_epoch: DEFAULT_SLOTS_PER_EPOCH,
stakers_slot_offset: DEFAULT_SLOTS_PER_EPOCH,
ticks_per_slot: DEFAULT_TICKS_PER_SLOT,
slots_per_segment: DEFAULT_SLOTS_PER_SEGMENT,
poh_config: PohConfig::default(),
inflation: Inflation::default(),
fee_calculator: FeeCalculator::default(),
rent_calculator: RentCalculator::default(),
epoch_schedule: EpochSchedule::default(),
}
}
}
@ -69,7 +70,6 @@ impl Default for GenesisBlock {
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Builder {
genesis_block: GenesisBlock,
already_have_stakers_slot_offset: bool,
}
impl Builder {
@ -114,21 +114,8 @@ impl Builder {
Self::append(rewards_pools, self.genesis_block.rewards_pools);
self
}
// also sets stakers_slot_offset, unless already set explicitly
pub fn slots_per_epoch(mut self, slots_per_epoch: u64) -> Self {
self.genesis_block.slots_per_epoch = slots_per_epoch;
if !self.already_have_stakers_slot_offset {
self.genesis_block.stakers_slot_offset = slots_per_epoch;
}
self
}
pub fn stakers_slot_offset(mut self, stakers_slot_offset: u64) -> Self {
self.genesis_block.stakers_slot_offset = stakers_slot_offset;
self.already_have_stakers_slot_offset = true;
self
}
pub fn epoch_warmup(mut self, epoch_warmup: bool) -> Self {
self.genesis_block.epoch_warmup = epoch_warmup;
pub fn epoch_schedule(mut self, epoch_schedule: EpochSchedule) -> Self {
self.genesis_block.epoch_schedule = epoch_schedule;
self
}
pub fn ticks_per_slot(mut self, ticks_per_slot: u64) -> Self {

View File

@ -2,6 +2,7 @@ pub mod account;
pub mod account_utils;
pub mod bpf_loader;
pub mod clock;
pub mod epoch_schedule;
pub mod fee_calculator;
pub mod hash;
pub mod inflation;
@ -17,6 +18,7 @@ pub mod pubkey;
pub mod rent_calculator;
pub mod rpc_port;
pub mod short_vec;
pub mod slot_hashes;
pub mod system_instruction;
pub mod system_program;
pub mod sysvar;

77
sdk/src/slot_hashes.rs Normal file
View File

@ -0,0 +1,77 @@
//! named accounts for synthesized data accounts for bank state, etc.
//!
//! this account carries the Bank's most recent blockhashes for some N parents
//!
use crate::hash::Hash;
use std::ops::Deref;
pub const MAX_SLOT_HASHES: usize = 512; // 512 slots to get your vote in
pub use crate::clock::Slot;
pub type SlotHash = (Slot, Hash);
#[repr(C)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
pub struct SlotHashes(Vec<SlotHash>);
impl SlotHashes {
pub fn add(&mut self, slot: Slot, hash: Hash) {
match self.binary_search_by(|probe| slot.cmp(&probe.0)) {
Ok(index) => (self.0)[index] = (slot, hash),
Err(index) => (self.0).insert(index, (slot, hash)),
}
(self.0).truncate(MAX_SLOT_HASHES);
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get(&self, slot: &Slot) -> Option<&Hash> {
self.binary_search_by(|probe| slot.cmp(&probe.0))
.ok()
.map(|index| &self[index].1)
}
pub fn new(slot_hashes: &[SlotHash]) -> Self {
let mut slot_hashes = slot_hashes.to_vec();
slot_hashes.sort_by(|a, b| b.0.cmp(&a.0)); // reverse order
Self(slot_hashes)
}
}
impl Deref for SlotHashes {
type Target = Vec<SlotHash>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::hash;
#[test]
fn test() {
let mut slot_hashes = SlotHashes::new(&[(1, Hash::default()), (3, Hash::default())]);
slot_hashes.add(2, Hash::default());
assert_eq!(
slot_hashes,
SlotHashes(vec![
(3, Hash::default()),
(2, Hash::default()),
(1, Hash::default()),
])
);
let mut slot_hashes = SlotHashes::new(&[]);
for i in 0..MAX_SLOT_HASHES + 1 {
slot_hashes.add(
i as u64,
hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
);
}
for i in 0..MAX_SLOT_HASHES {
assert_eq!(slot_hashes[i].0, (MAX_SLOT_HASHES - i) as u64);
}
assert_eq!(slot_hashes.len(), MAX_SLOT_HASHES);
}
}

View File

@ -0,0 +1,57 @@
//! This account contains the current cluster rent
//!
use crate::{
account::{Account, KeyedAccount},
account_info::AccountInfo,
epoch_schedule::EpochSchedule,
instruction::InstructionError,
sysvar,
};
/// epoch_schedule account pubkey
const ID: [u8; 32] = [
6, 167, 213, 23, 24, 220, 63, 238, 2, 211, 228, 127, 1, 0, 248, 176, 84, 247, 148, 46, 96, 89,
30, 63, 80, 135, 25, 168, 5, 0, 0, 0,
];
crate::solana_name_id!(ID, "SysvarEpochSchedu1e111111111111111111111111");
impl EpochSchedule {
pub fn deserialize(account: &Account) -> Result<Self, bincode::Error> {
account.deserialize_data()
}
pub fn from_account(account: &Account) -> Option<Self> {
account.deserialize_data().ok()
}
pub fn to_account(&self, account: &mut Account) -> Option<()> {
account.serialize_data(self).ok()
}
pub fn from_account_info(account: &AccountInfo) -> Option<Self> {
account.deserialize_data().ok()
}
pub fn to_account_info(&self, account: &mut AccountInfo) -> Option<()> {
account.serialize_data(self).ok()
}
pub fn from_keyed_account(account: &KeyedAccount) -> Result<EpochSchedule, InstructionError> {
if !check_id(account.unsigned_key()) {
return Err(InstructionError::InvalidArgument);
}
EpochSchedule::from_account(account.account).ok_or(InstructionError::InvalidArgument)
}
}
pub fn create_account(lamports: u64, epoch_schedule: &EpochSchedule) -> Account {
Account::new_data(lamports, epoch_schedule, &sysvar::id()).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_account() {
let account = create_account(42, &EpochSchedule::default());
let epoch_schedule = EpochSchedule::from_account(&account).unwrap();
assert_eq!(epoch_schedule, EpochSchedule::default());
}
}

View File

@ -3,6 +3,7 @@
use crate::pubkey::Pubkey;
pub mod clock;
pub mod epoch_schedule;
pub mod fees;
pub mod rent;
pub mod rewards;

View File

@ -1,9 +1,12 @@
//! This account contains the current cluster rent
//!
use crate::account::Account;
use crate::account_info::AccountInfo;
use crate::rent_calculator::RentCalculator;
use crate::sysvar;
use crate::{
account::{Account, KeyedAccount},
account_info::AccountInfo,
instruction::InstructionError,
rent_calculator::RentCalculator,
sysvar,
};
use bincode::serialized_size;
/// rent account pubkey
@ -49,9 +52,6 @@ pub fn create_account(lamports: u64, rent_calculator: &RentCalculator) -> Accoun
.unwrap()
}
use crate::account::KeyedAccount;
use crate::instruction::InstructionError;
pub fn from_keyed_account(account: &KeyedAccount) -> Result<Rent, InstructionError> {
if !check_id(account.unsigned_key()) {
return Err(InstructionError::InvalidArgument);

View File

@ -58,7 +58,6 @@ use crate::account::KeyedAccount;
use crate::instruction::InstructionError;
pub fn from_keyed_account(account: &KeyedAccount) -> Result<Rewards, InstructionError> {
if !check_id(account.unsigned_key()) {
dbg!(account.unsigned_key());
return Err(InstructionError::InvalidArgument);
}
Rewards::from_account(account.account).ok_or(InstructionError::InvalidAccountData)

View File

@ -2,14 +2,9 @@
//!
//! this account carries the Bank's most recent blockhashes for some N parents
//!
use crate::account::Account;
use crate::account_info::AccountInfo;
use crate::hash::Hash;
use crate::sysvar;
pub use crate::slot_hashes::{SlotHash, SlotHashes};
use crate::{account::Account, account_info::AccountInfo, sysvar};
use bincode::serialized_size;
use std::ops::Deref;
pub use crate::clock::Slot;
const ID: [u8; 32] = [
6, 167, 213, 23, 25, 47, 10, 175, 198, 242, 101, 227, 251, 119, 204, 122, 218, 130, 197, 41,
@ -20,11 +15,6 @@ crate::solana_name_id!(ID, "SysvarS1otHashes111111111111111111111111111");
pub const MAX_SLOT_HASHES: usize = 512; // 512 slots to get your vote in
pub type SlotHash = (Slot, Hash);
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct SlotHashes(Vec<SlotHash>);
impl SlotHashes {
pub fn from_account(account: &Account) -> Option<Self> {
account.deserialize_data().ok()
@ -39,30 +29,7 @@ impl SlotHashes {
account.serialize_data(self).ok()
}
pub fn size_of() -> usize {
serialized_size(&SlotHashes(vec![(0, Hash::default()); MAX_SLOT_HASHES])).unwrap() as usize
}
pub fn add(&mut self, slot: Slot, hash: Hash) {
match self.binary_search_by(|probe| slot.cmp(&probe.0)) {
Ok(index) => (self.0)[index] = (slot, hash),
Err(index) => (self.0).insert(index, (slot, hash)),
}
(self.0).truncate(MAX_SLOT_HASHES);
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn get(&self, slot: &Slot) -> Option<&Hash> {
self.binary_search_by(|probe| slot.cmp(&probe.0))
.ok()
.map(|index| &self[index].1)
}
pub fn new(slot_hashes: &[SlotHash]) -> Self {
Self(slot_hashes.to_vec())
}
}
impl Deref for SlotHashes {
type Target = Vec<SlotHash>;
fn deref(&self) -> &Self::Target {
&self.0
serialized_size(&SlotHashes::new(&[SlotHash::default(); MAX_SLOT_HASHES])).unwrap() as usize
}
}
@ -86,7 +53,6 @@ pub fn from_keyed_account(account: &KeyedAccount) -> Result<SlotHashes, Instruct
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::hash;
#[test]
fn test_create_account() {
@ -94,18 +60,6 @@ mod tests {
let account = create_account(lamports, &[]);
assert_eq!(account.data.len(), SlotHashes::size_of());
let slot_hashes = SlotHashes::from_account(&account);
assert_eq!(slot_hashes, Some(SlotHashes(vec![])));
let mut slot_hashes = slot_hashes.unwrap();
for i in 0..MAX_SLOT_HASHES + 1 {
slot_hashes.add(
i as u64,
hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
);
}
for i in 0..MAX_SLOT_HASHES {
assert_eq!(slot_hashes[i].0, (MAX_SLOT_HASHES - i) as u64);
}
assert_eq!(slot_hashes.len(), MAX_SLOT_HASHES);
assert_eq!(slot_hashes, Some(SlotHashes::default()));
}
}

View File

@ -24,6 +24,7 @@ pub struct StakeHistoryEntry {
pub deactivating: u64, // requested to be cooled down, not fully deactivated yet
}
#[repr(C)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>);