use {
crate::{
account_info::AccountInfo,
hash::Hash,
program_error::ProgramError,
slot_hashes::MAX_ENTRIES,
sysvar::{get_sysvar, Sysvar},
},
bytemuck_derive::{Pod, Zeroable},
solana_clock::Slot,
};
const U64_SIZE: usize = std::mem::size_of::<u64>();
pub use {
solana_slot_hashes::{
sysvar::{check_id, id, ID},
SlotHashes,
},
solana_sysvar_id::SysvarId,
};
impl Sysvar for SlotHashes {
fn size_of() -> usize {
20_488 }
fn from_account_info(_account_info: &AccountInfo) -> Result<Self, ProgramError> {
Err(ProgramError::UnsupportedSysvar)
}
}
#[derive(Copy, Clone, Default, Pod, Zeroable)]
#[repr(C)]
pub struct PodSlotHash {
pub slot: Slot,
pub hash: Hash,
}
#[derive(Default)]
pub struct PodSlotHashes {
data: Vec<u8>,
slot_hashes_start: usize,
slot_hashes_end: usize,
}
impl PodSlotHashes {
pub fn fetch() -> Result<Self, ProgramError> {
let sysvar_len = SlotHashes::size_of();
let mut data = vec![0; sysvar_len];
if data.as_ptr().align_offset(8) != 0 {
return Err(ProgramError::InvalidAccountData);
}
get_sysvar(
&mut data,
&SlotHashes::id(),
0,
sysvar_len as u64,
)?;
let length = data
.get(..U64_SIZE)
.and_then(|bytes| bytes.try_into().ok())
.map(u64::from_le_bytes)
.and_then(|length| length.checked_mul(std::mem::size_of::<PodSlotHash>() as u64))
.ok_or(ProgramError::InvalidAccountData)?;
let slot_hashes_start = U64_SIZE;
let slot_hashes_end = slot_hashes_start.saturating_add(length as usize);
Ok(Self {
data,
slot_hashes_start,
slot_hashes_end,
})
}
pub fn as_slice(&self) -> Result<&[PodSlotHash], ProgramError> {
self.data
.get(self.slot_hashes_start..self.slot_hashes_end)
.and_then(|data| bytemuck::try_cast_slice(data).ok())
.ok_or(ProgramError::InvalidAccountData)
}
pub fn get(&self, slot: &Slot) -> Result<Option<Hash>, ProgramError> {
self.as_slice().map(|pod_hashes| {
pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.map(|idx| pod_hashes[idx].hash)
.ok()
})
}
pub fn position(&self, slot: &Slot) -> Result<Option<usize>, ProgramError> {
self.as_slice().map(|pod_hashes| {
pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.ok()
})
}
}
#[deprecated(since = "2.1.0", note = "Please use `PodSlotHashes` instead")]
pub struct SlotHashesSysvar;
#[allow(deprecated)]
impl SlotHashesSysvar {
pub fn get(slot: &Slot) -> Result<Option<Hash>, ProgramError> {
get_pod_slot_hashes().map(|pod_hashes| {
pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.map(|idx| pod_hashes[idx].hash)
.ok()
})
}
pub fn position(slot: &Slot) -> Result<Option<usize>, ProgramError> {
get_pod_slot_hashes().map(|pod_hashes| {
pod_hashes
.binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
.ok()
})
}
}
fn get_pod_slot_hashes() -> Result<Vec<PodSlotHash>, ProgramError> {
let mut pod_hashes = vec![PodSlotHash::default(); MAX_ENTRIES];
{
let data = bytemuck::try_cast_slice_mut::<PodSlotHash, u8>(&mut pod_hashes)
.map_err(|_| ProgramError::InvalidAccountData)?;
if data.as_ptr().align_offset(8) != 0 {
return Err(ProgramError::InvalidAccountData);
}
let offset = 8; let length = (SlotHashes::size_of() as u64).saturating_sub(offset);
get_sysvar(data, &SlotHashes::id(), offset, length)?;
}
Ok(pod_hashes)
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
hash::{hash, Hash},
slot_hashes::MAX_ENTRIES,
sysvar::tests::mock_get_sysvar_syscall,
},
serial_test::serial,
test_case::test_case,
};
#[test]
fn test_size_of() {
assert_eq!(
SlotHashes::size_of(),
bincode::serialized_size(
&(0..MAX_ENTRIES)
.map(|slot| (slot as Slot, Hash::default()))
.collect::<SlotHashes>()
)
.unwrap() as usize
);
}
fn mock_slot_hashes(slot_hashes: &SlotHashes) {
let mut data = vec![0; SlotHashes::size_of()];
bincode::serialize_into(&mut data[..], slot_hashes).unwrap();
mock_get_sysvar_syscall(&data);
}
#[test_case(0)]
#[test_case(1)]
#[test_case(2)]
#[test_case(5)]
#[test_case(10)]
#[test_case(64)]
#[test_case(128)]
#[test_case(192)]
#[test_case(256)]
#[test_case(384)]
#[test_case(MAX_ENTRIES)]
#[serial]
fn test_pod_slot_hashes(num_entries: usize) {
let mut slot_hashes = vec![];
for i in 0..num_entries {
slot_hashes.push((
i as u64,
hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
));
}
let check_slot_hashes = SlotHashes::new(&slot_hashes);
mock_slot_hashes(&check_slot_hashes);
let pod_slot_hashes = PodSlotHashes::fetch().unwrap();
let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
assert_eq!(
pod_slot_hashes.get(slot).unwrap().as_ref(),
check_slot_hashes.get(slot),
);
assert_eq!(
pod_slot_hashes.position(slot).unwrap(),
check_slot_hashes.position(slot),
);
}
let not_a_slot = num_entries.saturating_add(1) as u64;
assert_eq!(
pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
check_slot_hashes.get(¬_a_slot),
);
assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
assert_eq!(
pod_slot_hashes.position(¬_a_slot).unwrap(),
check_slot_hashes.position(¬_a_slot),
);
assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
let not_a_slot = num_entries.saturating_add(2) as u64;
assert_eq!(
pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
check_slot_hashes.get(¬_a_slot),
);
assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
assert_eq!(
pod_slot_hashes.position(¬_a_slot).unwrap(),
check_slot_hashes.position(¬_a_slot),
);
assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
}
#[allow(deprecated)]
#[serial]
#[test]
fn test_slot_hashes_sysvar() {
let mut slot_hashes = vec![];
for i in 0..MAX_ENTRIES {
slot_hashes.push((
i as u64,
hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
));
}
let check_slot_hashes = SlotHashes::new(&slot_hashes);
mock_get_sysvar_syscall(&bincode::serialize(&check_slot_hashes).unwrap());
assert_eq!(
SlotHashesSysvar::get(&0).unwrap().as_ref(),
check_slot_hashes.get(&0),
);
assert_eq!(
SlotHashesSysvar::get(&256).unwrap().as_ref(),
check_slot_hashes.get(&256),
);
assert_eq!(
SlotHashesSysvar::get(&511).unwrap().as_ref(),
check_slot_hashes.get(&511),
);
assert_eq!(
SlotHashesSysvar::get(&600).unwrap().as_ref(),
check_slot_hashes.get(&600),
);
assert_eq!(
SlotHashesSysvar::position(&0).unwrap(),
check_slot_hashes.position(&0),
);
assert_eq!(
SlotHashesSysvar::position(&256).unwrap(),
check_slot_hashes.position(&256),
);
assert_eq!(
SlotHashesSysvar::position(&511).unwrap(),
check_slot_hashes.position(&511),
);
assert_eq!(
SlotHashesSysvar::position(&600).unwrap(),
check_slot_hashes.position(&600),
);
}
}