pub use crate::stake_history::StakeHistory;
use {
crate::{
stake_history::{StakeHistoryEntry, StakeHistoryGetEntry, MAX_ENTRIES},
sysvar::{get_sysvar, Sysvar, SysvarId},
},
solana_clock::Epoch,
solana_sysvar_id::declare_sysvar_id,
};
declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory);
impl Sysvar for StakeHistory {
fn size_of() -> usize {
16392 }
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct StakeHistorySysvar(pub Epoch);
const EPOCH_AND_ENTRY_SERIALIZED_SIZE: u64 = 32;
impl StakeHistoryGetEntry for StakeHistorySysvar {
fn get_entry(&self, target_epoch: Epoch) -> Option<StakeHistoryEntry> {
let current_epoch = self.0;
let newest_historical_epoch = current_epoch.checked_sub(1)?;
let oldest_historical_epoch = current_epoch.saturating_sub(MAX_ENTRIES as u64);
if target_epoch < oldest_historical_epoch {
return None;
}
let epoch_delta = newest_historical_epoch.checked_sub(target_epoch)?;
let offset = epoch_delta
.checked_mul(EPOCH_AND_ENTRY_SERIALIZED_SIZE)?
.checked_add(std::mem::size_of::<u64>() as u64)?;
let mut entry_buf = [0; EPOCH_AND_ENTRY_SERIALIZED_SIZE as usize];
let result = get_sysvar(
&mut entry_buf,
&StakeHistory::id(),
offset,
EPOCH_AND_ENTRY_SERIALIZED_SIZE,
);
match result {
Ok(()) => {
let (entry_epoch, entry) =
bincode::deserialize::<(Epoch, StakeHistoryEntry)>(&entry_buf).ok()?;
assert_eq!(entry_epoch, target_epoch);
Some(entry)
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{stake_history::*, sysvar::tests::mock_get_sysvar_syscall},
serial_test::serial,
};
#[test]
fn test_size_of() {
let mut stake_history = StakeHistory::default();
for i in 0..MAX_ENTRIES as u64 {
stake_history.add(
i,
StakeHistoryEntry {
activating: i,
..StakeHistoryEntry::default()
},
);
}
assert_eq!(
bincode::serialized_size(&stake_history).unwrap() as usize,
StakeHistory::size_of()
);
let stake_history_inner: Vec<(Epoch, StakeHistoryEntry)> =
bincode::deserialize(&bincode::serialize(&stake_history).unwrap()).unwrap();
let epoch_entry = stake_history_inner.into_iter().next().unwrap();
assert_eq!(
bincode::serialized_size(&epoch_entry).unwrap(),
EPOCH_AND_ENTRY_SERIALIZED_SIZE
);
}
#[serial]
#[test]
fn test_stake_history_get_entry() {
let unique_entry_for_epoch = |epoch: u64| StakeHistoryEntry {
activating: epoch.saturating_mul(2),
deactivating: epoch.saturating_mul(3),
effective: epoch.saturating_mul(5),
};
let current_epoch = MAX_ENTRIES.saturating_add(2) as u64;
let mut stake_history = StakeHistory::default();
for i in 0..current_epoch {
stake_history.add(i, unique_entry_for_epoch(i));
}
assert_eq!(stake_history.len(), MAX_ENTRIES);
assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 2);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), None);
assert_eq!(stake_history.get(1), None);
assert_eq!(stake_history.get(current_epoch), None);
assert_eq!(stake_history.get_entry(0), None);
assert_eq!(stake_history.get_entry(1), None);
assert_eq!(stake_history.get_entry(current_epoch), None);
assert_eq!(stake_history_sysvar.get_entry(0), None);
assert_eq!(stake_history_sysvar.get_entry(1), None);
assert_eq!(stake_history_sysvar.get_entry(current_epoch), None);
for i in 2..current_epoch {
let entry = Some(unique_entry_for_epoch(i));
assert_eq!(stake_history.get(i), entry.as_ref(),);
assert_eq!(stake_history.get_entry(i), entry,);
assert_eq!(stake_history_sysvar.get_entry(i), entry,);
}
}
#[serial]
#[test]
fn test_stake_history_get_entry_zero() {
let mut current_epoch = 0;
let stake_history = StakeHistory::default();
assert_eq!(stake_history.len(), 0);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), None);
assert_eq!(stake_history.get_entry(0), None);
assert_eq!(stake_history_sysvar.get_entry(0), None);
let entry_zero = StakeHistoryEntry {
effective: 100,
..StakeHistoryEntry::default()
};
let entry = Some(entry_zero.clone());
let mut stake_history = StakeHistory::default();
stake_history.add(current_epoch, entry_zero);
assert_eq!(stake_history.len(), 1);
current_epoch = current_epoch.saturating_add(1);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), entry.as_ref());
assert_eq!(stake_history.get_entry(0), entry);
assert_eq!(stake_history_sysvar.get_entry(0), entry);
stake_history.add(current_epoch, StakeHistoryEntry::default());
assert_eq!(stake_history.len(), 2);
current_epoch = current_epoch.saturating_add(1);
mock_get_sysvar_syscall(&bincode::serialize(&stake_history).unwrap());
let stake_history_sysvar = StakeHistorySysvar(current_epoch);
assert_eq!(stake_history.get(0), entry.as_ref());
assert_eq!(stake_history.get_entry(0), entry);
assert_eq!(stake_history_sysvar.get_entry(0), entry);
}
}