forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 262
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sdk: Extract slot-history sysvar (#3249)
* sdk: Extract slot-history sysvar #### Problem Just about all of the sysvars exist in separate crates, except for slot history. #### Summary of changes Extract it into a new crate. Looks pretty similar to slot hashes, except all of the `info!` calls were removed in the tests. * Remove clock dependency * Update doc link * Fix rebase errors * Update doc link (again) * Add deprecation warning
- Loading branch information
Showing
7 changed files
with
253 additions
and
211 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,211 +1,2 @@ | ||
//! A type to hold data for the [`SlotHistory` sysvar][sv]. | ||
//! | ||
//! [sv]: https://docs.solanalabs.com/runtime/sysvars#slothistory | ||
//! | ||
//! The sysvar ID is declared in [`sysvar::slot_history`]. | ||
//! | ||
//! [`sysvar::slot_history`]: crate::sysvar::slot_history | ||
#![allow(clippy::arithmetic_side_effects)] | ||
pub use crate::clock::Slot; | ||
use bv::{BitVec, BitsMut}; | ||
|
||
/// A bitvector indicating which slots are present in the past epoch. | ||
#[repr(C)] | ||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
pub struct SlotHistory { | ||
pub bits: BitVec<u64>, | ||
pub next_slot: Slot, | ||
} | ||
|
||
impl Default for SlotHistory { | ||
fn default() -> Self { | ||
let mut bits = BitVec::new_fill(false, MAX_ENTRIES); | ||
bits.set(0, true); | ||
Self { bits, next_slot: 1 } | ||
} | ||
} | ||
|
||
impl std::fmt::Debug for SlotHistory { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "SlotHistory {{ slot: {} bits:", self.next_slot)?; | ||
for i in 0..MAX_ENTRIES { | ||
if self.bits.get(i) { | ||
write!(f, "1")?; | ||
} else { | ||
write!(f, "0")?; | ||
} | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
pub const MAX_ENTRIES: u64 = 1024 * 1024; // 1 million slots is about 5 days | ||
|
||
#[derive(PartialEq, Eq, Debug)] | ||
pub enum Check { | ||
Future, | ||
TooOld, | ||
Found, | ||
NotFound, | ||
} | ||
|
||
impl SlotHistory { | ||
pub fn add(&mut self, slot: Slot) { | ||
if slot > self.next_slot && slot - self.next_slot >= MAX_ENTRIES { | ||
// Wrapped past current history, | ||
// clear entire bitvec. | ||
let full_blocks = (MAX_ENTRIES as usize) / 64; | ||
for i in 0..full_blocks { | ||
self.bits.set_block(i, 0); | ||
} | ||
} else { | ||
for skipped in self.next_slot..slot { | ||
self.bits.set(skipped % MAX_ENTRIES, false); | ||
} | ||
} | ||
self.bits.set(slot % MAX_ENTRIES, true); | ||
self.next_slot = slot + 1; | ||
} | ||
|
||
pub fn check(&self, slot: Slot) -> Check { | ||
if slot > self.newest() { | ||
Check::Future | ||
} else if slot < self.oldest() { | ||
Check::TooOld | ||
} else if self.bits.get(slot % MAX_ENTRIES) { | ||
Check::Found | ||
} else { | ||
Check::NotFound | ||
} | ||
} | ||
|
||
pub fn oldest(&self) -> Slot { | ||
self.next_slot.saturating_sub(MAX_ENTRIES) | ||
} | ||
|
||
pub fn newest(&self) -> Slot { | ||
self.next_slot - 1 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use {super::*, log::*}; | ||
|
||
#[test] | ||
fn slot_history_test1() { | ||
solana_logger::setup(); | ||
// should be divisible by 64 since the clear logic works on blocks | ||
assert_eq!(MAX_ENTRIES % 64, 0); | ||
let mut slot_history = SlotHistory::default(); | ||
info!("add 2"); | ||
slot_history.add(2); | ||
assert_eq!(slot_history.check(0), Check::Found); | ||
assert_eq!(slot_history.check(1), Check::NotFound); | ||
for i in 3..MAX_ENTRIES { | ||
assert_eq!(slot_history.check(i), Check::Future); | ||
} | ||
info!("add 20"); | ||
slot_history.add(20); | ||
info!("add max_entries"); | ||
slot_history.add(MAX_ENTRIES); | ||
assert_eq!(slot_history.check(0), Check::TooOld); | ||
assert_eq!(slot_history.check(1), Check::NotFound); | ||
for i in &[2, 20, MAX_ENTRIES] { | ||
assert_eq!(slot_history.check(*i), Check::Found); | ||
} | ||
for i in 3..20 { | ||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}"); | ||
} | ||
for i in 21..MAX_ENTRIES { | ||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}"); | ||
} | ||
assert_eq!(slot_history.check(MAX_ENTRIES + 1), Check::Future); | ||
|
||
info!("add max_entries + 3"); | ||
let slot = 3 * MAX_ENTRIES + 3; | ||
slot_history.add(slot); | ||
for i in &[0, 1, 2, 20, 21, MAX_ENTRIES] { | ||
assert_eq!(slot_history.check(*i), Check::TooOld); | ||
} | ||
let start = slot - MAX_ENTRIES + 1; | ||
let end = slot; | ||
for i in start..end { | ||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}"); | ||
} | ||
assert_eq!(slot_history.check(slot), Check::Found); | ||
} | ||
|
||
#[test] | ||
fn slot_history_test_wrap() { | ||
solana_logger::setup(); | ||
let mut slot_history = SlotHistory::default(); | ||
info!("add 2"); | ||
slot_history.add(2); | ||
assert_eq!(slot_history.check(0), Check::Found); | ||
assert_eq!(slot_history.check(1), Check::NotFound); | ||
for i in 3..MAX_ENTRIES { | ||
assert_eq!(slot_history.check(i), Check::Future); | ||
} | ||
info!("add 20"); | ||
slot_history.add(20); | ||
info!("add max_entries + 19"); | ||
slot_history.add(MAX_ENTRIES + 19); | ||
for i in 0..19 { | ||
assert_eq!(slot_history.check(i), Check::TooOld); | ||
} | ||
assert_eq!(slot_history.check(MAX_ENTRIES), Check::NotFound); | ||
assert_eq!(slot_history.check(20), Check::Found); | ||
assert_eq!(slot_history.check(MAX_ENTRIES + 19), Check::Found); | ||
assert_eq!(slot_history.check(20), Check::Found); | ||
for i in 21..MAX_ENTRIES + 19 { | ||
assert_eq!(slot_history.check(i), Check::NotFound, "found: {i}"); | ||
} | ||
assert_eq!(slot_history.check(MAX_ENTRIES + 20), Check::Future); | ||
} | ||
|
||
#[test] | ||
fn slot_history_test_same_index() { | ||
solana_logger::setup(); | ||
let mut slot_history = SlotHistory::default(); | ||
info!("add 3,4"); | ||
slot_history.add(3); | ||
slot_history.add(4); | ||
assert_eq!(slot_history.check(1), Check::NotFound); | ||
assert_eq!(slot_history.check(2), Check::NotFound); | ||
assert_eq!(slot_history.check(3), Check::Found); | ||
assert_eq!(slot_history.check(4), Check::Found); | ||
slot_history.add(MAX_ENTRIES + 5); | ||
assert_eq!(slot_history.check(5), Check::TooOld); | ||
for i in 6..MAX_ENTRIES + 5 { | ||
assert_eq!(slot_history.check(i), Check::NotFound, "i: {i}"); | ||
} | ||
assert_eq!(slot_history.check(MAX_ENTRIES + 5), Check::Found); | ||
} | ||
|
||
#[test] | ||
fn test_older_slot() { | ||
let mut slot_history = SlotHistory::default(); | ||
slot_history.add(10); | ||
slot_history.add(5); | ||
assert_eq!(slot_history.check(0), Check::Found); | ||
assert_eq!(slot_history.check(5), Check::Found); | ||
// If we go backwards we reset? | ||
assert_eq!(slot_history.check(10), Check::Future); | ||
assert_eq!(slot_history.check(6), Check::Future); | ||
assert_eq!(slot_history.check(11), Check::Future); | ||
} | ||
|
||
#[test] | ||
fn test_oldest() { | ||
let mut slot_history = SlotHistory::default(); | ||
assert_eq!(slot_history.oldest(), 0); | ||
slot_history.add(10); | ||
assert_eq!(slot_history.oldest(), 0); | ||
slot_history.add(MAX_ENTRIES - 1); | ||
assert_eq!(slot_history.oldest(), 0); | ||
slot_history.add(MAX_ENTRIES); | ||
assert_eq!(slot_history.oldest(), 1); | ||
} | ||
} | ||
#[deprecated(since = "2.1.0", note = "Use `solana-slot-history` crate instead")] | ||
pub use {solana_clock::Slot, solana_slot_history::*}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "solana-slot-history" | ||
description = "Types and utilities for the Solana SlotHistory sysvar." | ||
documentation = "https://docs.rs/solana-slot-history" | ||
version = { workspace = true } | ||
authors = { workspace = true } | ||
repository = { workspace = true } | ||
homepage = { workspace = true } | ||
license = { workspace = true } | ||
edition = { workspace = true } | ||
|
||
[dependencies] | ||
bv = { workspace = true } | ||
serde = { workspace = true, optional = true } | ||
serde_derive = { workspace = true, optional = true } | ||
|
||
[features] | ||
serde = ["dep:serde", "dep:serde_derive", "bv/serde"] | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
all-features = true | ||
rustdoc-args = ["--cfg=docsrs"] |
Oops, something went wrong.