diff --git a/Cargo.lock b/Cargo.lock index 8ad2c06026e682..6f60e7438130f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7285,6 +7285,7 @@ dependencies = [ "solana-serialize-utils", "solana-sha256-hasher", "solana-short-vec", + "solana-slot-hashes", "static_assertions", "test-case", "thiserror", @@ -7971,6 +7972,16 @@ dependencies = [ "solana-sanitize", ] +[[package]] +name = "solana-slot-hashes" +version = "2.1.0" +dependencies = [ + "serde", + "serde_derive", + "solana-hash", + "solana-sha256-hasher", +] + [[package]] name = "solana-stake-accounts" version = "2.1.0" diff --git a/Cargo.toml b/Cargo.toml index bbcef11c5c94b0..cf5c1f5f2ceb9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,6 +128,7 @@ members = [ "sdk/serialize-utils", "sdk/sha256-hasher", "sdk/signature", + "sdk/slot-hashes", "send-transaction-service", "short-vec", "stake-accounts", @@ -413,7 +414,7 @@ solana-genesis-utils = { path = "genesis-utils", version = "=2.1.0" } agave-geyser-plugin-interface = { path = "geyser-plugin-interface", version = "=2.1.0" } solana-geyser-plugin-manager = { path = "geyser-plugin-manager", version = "=2.1.0" } solana-gossip = { path = "gossip", version = "=2.1.0" } -solana-hash = { path = "sdk/hash", version = "=2.1.0" } +solana-hash = { path = "sdk/hash", version = "=2.1.0", default-features = false } solana-inline-spl = { path = "inline-spl", version = "=2.1.0" } solana-instruction = { path = "sdk/instruction", version = "=2.1.0", default-features = false } solana-lattice-hash = { path = "lattice-hash", version = "=2.1.0" } @@ -451,6 +452,7 @@ solana-serde-varint = { path = "sdk/serde-varint", version = "=2.1.0" } 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-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" } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 57bcdd73334554..41a574125fd1d1 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5682,6 +5682,7 @@ dependencies = [ "solana-serialize-utils", "solana-sha256-hasher", "solana-short-vec", + "solana-slot-hashes", "thiserror", "wasm-bindgen", ] @@ -6721,6 +6722,15 @@ dependencies = [ "solana-sanitize", ] +[[package]] +name = "solana-slot-hashes" +version = "2.1.0" +dependencies = [ + "serde", + "serde_derive", + "solana-hash", +] + [[package]] name = "solana-stake-program" version = "2.1.0" diff --git a/sdk/program/Cargo.toml b/sdk/program/Cargo.toml index 7acba606490664..21240fe1db3edc 100644 --- a/sdk/program/Cargo.toml +++ b/sdk/program/Cargo.toml @@ -62,6 +62,7 @@ solana-serde-varint = { workspace = true } solana-serialize-utils = { workspace = true } solana-sha256-hasher = { workspace = true, features = ["sha2"] } solana-short-vec = { workspace = true } +solana-slot-hashes = { workspace = true, features = ["serde"] } thiserror = { workspace = true } # This is currently needed to build on-chain programs reliably. diff --git a/sdk/program/src/slot_hashes.rs b/sdk/program/src/slot_hashes.rs index f18d14e89f9e9c..7113b2345641c9 100644 --- a/sdk/program/src/slot_hashes.rs +++ b/sdk/program/src/slot_hashes.rs @@ -1,109 +1 @@ -//! A type to hold data for the [`SlotHashes` sysvar][sv]. -//! -//! [sv]: https://docs.solanalabs.com/runtime/sysvars#slothashes -//! -//! The sysvar ID is declared in [`sysvar::slot_hashes`]. -//! -//! [`sysvar::slot_hashes`]: crate::sysvar::slot_hashes - -pub use solana_clock::Slot; -use { - crate::hash::Hash, - std::{ - iter::FromIterator, - ops::Deref, - sync::atomic::{AtomicUsize, Ordering}, - }, -}; - -pub const MAX_ENTRIES: usize = 512; // about 2.5 minutes to get your vote in - -// This is to allow tests with custom slot hash expiry to avoid having to generate -// 512 blocks for such tests. -static NUM_ENTRIES: AtomicUsize = AtomicUsize::new(MAX_ENTRIES); - -pub fn get_entries() -> usize { - NUM_ENTRIES.load(Ordering::Relaxed) -} - -pub fn set_entries_for_tests_only(entries: usize) { - NUM_ENTRIES.store(entries, Ordering::Relaxed); -} - -pub type SlotHash = (Slot, Hash); - -#[repr(C)] -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default)] -pub struct SlotHashes(Vec); - -impl SlotHashes { - pub fn add(&mut self, slot: Slot, hash: Hash) { - match self.binary_search_by(|(probe, _)| slot.cmp(probe)) { - Ok(index) => (self.0)[index] = (slot, hash), - Err(index) => (self.0).insert(index, (slot, hash)), - } - (self.0).truncate(get_entries()); - } - pub fn position(&self, slot: &Slot) -> Option { - self.binary_search_by(|(probe, _)| slot.cmp(probe)).ok() - } - #[allow(clippy::trivially_copy_pass_by_ref)] - pub fn get(&self, slot: &Slot) -> Option<&Hash> { - self.binary_search_by(|(probe, _)| slot.cmp(probe)) - .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.cmp(a)); - Self(slot_hashes) - } - pub fn slot_hashes(&self) -> &[SlotHash] { - &self.0 - } -} - -impl FromIterator<(Slot, Hash)> for SlotHashes { - fn from_iter>(iter: I) -> Self { - Self(iter.into_iter().collect()) - } -} - -impl Deref for SlotHashes { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use {super::*, 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_ENTRIES + 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_ENTRIES { - assert_eq!(slot_hashes[i].0, (MAX_ENTRIES - i) as u64); - } - - assert_eq!(slot_hashes.len(), MAX_ENTRIES); - } -} +pub use {solana_clock::Slot, solana_slot_hashes::*}; diff --git a/sdk/slot-hashes/Cargo.toml b/sdk/slot-hashes/Cargo.toml new file mode 100644 index 00000000000000..a5dff397507619 --- /dev/null +++ b/sdk/slot-hashes/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "solana-slot-hashes" +description = "Types and utilities for the Solana SlotHashes sysvar." +documentation = "https://docs.rs/solana-slot-hashes" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-hash = { workspace = true, default-features = false } + +[dev-dependencies] +solana-sha256-hasher = { workspace = true } + +[features] +serde = ["dep:serde", "dep:serde_derive", "solana-hash/serde"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/sdk/slot-hashes/src/lib.rs b/sdk/slot-hashes/src/lib.rs new file mode 100644 index 00000000000000..66be2f4b3b0eb8 --- /dev/null +++ b/sdk/slot-hashes/src/lib.rs @@ -0,0 +1,112 @@ +//! A type to hold data for the [`SlotHashes` sysvar][sv]. +//! +//! [sv]: https://docs.solanalabs.com/runtime/sysvars#slothashes +//! +//! The sysvar ID is declared in [`solana_program::sysvar::slot_hashes`]. +//! +//! [`solana_program::sysvar::slot_hashes`]: https://docs.rs/solana-program/latest/solana_program/sysvar/slot_hashes/index.html + +use { + solana_hash::Hash, + std::{ + iter::FromIterator, + ops::Deref, + sync::atomic::{AtomicUsize, Ordering}, + }, +}; + +pub const MAX_ENTRIES: usize = 512; // about 2.5 minutes to get your vote in + +// This is to allow tests with custom slot hash expiry to avoid having to generate +// 512 blocks for such tests. +static NUM_ENTRIES: AtomicUsize = AtomicUsize::new(MAX_ENTRIES); + +pub fn get_entries() -> usize { + NUM_ENTRIES.load(Ordering::Relaxed) +} + +pub fn set_entries_for_tests_only(entries: usize) { + NUM_ENTRIES.store(entries, Ordering::Relaxed); +} + +pub type SlotHash = (u64, Hash); + +#[repr(C)] +#[cfg_attr( + feature = "serde", + derive(serde_derive::Deserialize, serde_derive::Serialize) +)] +#[derive(PartialEq, Eq, Debug, Default)] +pub struct SlotHashes(Vec); + +impl SlotHashes { + pub fn add(&mut self, slot: u64, hash: Hash) { + match self.binary_search_by(|(probe, _)| slot.cmp(probe)) { + Ok(index) => (self.0)[index] = (slot, hash), + Err(index) => (self.0).insert(index, (slot, hash)), + } + (self.0).truncate(get_entries()); + } + pub fn position(&self, slot: &u64) -> Option { + self.binary_search_by(|(probe, _)| slot.cmp(probe)).ok() + } + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn get(&self, slot: &u64) -> Option<&Hash> { + self.binary_search_by(|(probe, _)| slot.cmp(probe)) + .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.cmp(a)); + Self(slot_hashes) + } + pub fn slot_hashes(&self) -> &[SlotHash] { + &self.0 + } +} + +impl FromIterator<(u64, Hash)> for SlotHashes { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl Deref for SlotHashes { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use {super::*, solana_sha256_hasher::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_ENTRIES + 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_ENTRIES { + assert_eq!(slot_hashes[i].0, (MAX_ENTRIES - i) as u64); + } + + assert_eq!(slot_hashes.len(), MAX_ENTRIES); + } +}