Skip to content

Commit

Permalink
sdk: Extract slot-history sysvar (#3249)
Browse files Browse the repository at this point in the history
* 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
joncinque authored Oct 28, 2024
1 parent bbe69cc commit 0f7a4c7
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 211 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ members = [
"sdk/sha256-hasher",
"sdk/signature",
"sdk/slot-hashes",
"sdk/slot-history",
"sdk/stable-layout",
"sdk/transaction-error",
"send-transaction-service",
Expand Down Expand Up @@ -476,6 +477,7 @@ solana-serialize-utils = { path = "sdk/serialize-utils", version = "=2.1.0" }
solana-sha256-hasher = { path = "sdk/sha256-hasher", version = "=2.1.0" }
solana-signature = { path = "sdk/signature", version = "=2.1.0", default-features = false }
solana-slot-hashes = { path = "sdk/slot-hashes", version = "=2.1.0" }
solana-slot-history = { path = "sdk/slot-history", version = "=2.1.0" }
solana-timings = { path = "timings", version = "=2.1.0" }
solana-unified-scheduler-logic = { path = "unified-scheduler-logic", version = "=2.1.0" }
solana-unified-scheduler-pool = { path = "unified-scheduler-pool", version = "=2.1.0" }
Expand Down
10 changes: 10 additions & 0 deletions programs/sbf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ solana-serialize-utils = { workspace = true }
solana-sha256-hasher = { workspace = true, features = ["sha2"] }
solana-short-vec = { workspace = true }
solana-slot-hashes = { workspace = true, features = ["serde"] }
solana-slot-history = { workspace = true, features = ["serde"] }
solana-stable-layout = { workspace = true }
thiserror = { workspace = true }

Expand Down
213 changes: 2 additions & 211 deletions sdk/program/src/slot_history.rs
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::*};
23 changes: 23 additions & 0 deletions sdk/slot-history/Cargo.toml
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"]
Loading

0 comments on commit 0f7a4c7

Please sign in to comment.