diff --git a/sdk/benches/slot_history.rs b/sdk/benches/slot_history.rs index 6a4ac824ba..dd4e0fe050 100644 --- a/sdk/benches/slot_history.rs +++ b/sdk/benches/slot_history.rs @@ -13,3 +13,16 @@ fn bench_to_from_account(b: &mut Bencher) { slot_history = SlotHistory::from_account(&account).unwrap(); }); } + +#[bench] +fn bench_slot_history_add_new(b: &mut Bencher) { + let mut slot_history = SlotHistory::default(); + + let mut slot = 0; + b.iter(|| { + for _ in 0..5 { + slot_history.add(slot); + slot += 100000; + } + }); +} diff --git a/sdk/src/slot_history.rs b/sdk/src/slot_history.rs index c92e077582..3fa97392b0 100644 --- a/sdk/src/slot_history.rs +++ b/sdk/src/slot_history.rs @@ -3,9 +3,10 @@ //! pub use crate::clock::Slot; use bv::BitVec; +use bv::BitsMut; #[repr(C)] -#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[derive(Serialize, Deserialize, PartialEq)] pub struct SlotHistory { pub bits: BitVec, pub next_slot: Slot, @@ -19,6 +20,20 @@ impl Default for SlotHistory { } } +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, Debug)] @@ -31,8 +46,17 @@ pub enum Check { impl SlotHistory { pub fn add(&mut self, slot: Slot) { - for skipped in self.next_slot..slot { - self.bits.set(skipped % MAX_ENTRIES, false); + 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; @@ -54,19 +78,112 @@ impl SlotHistory { #[cfg(test)] mod tests { use super::*; + use log::*; #[test] - fn test() { + fn slot_history_test1() { + solana_logger::setup(); + // should be divisable 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); assert_eq!(slot_history.check(2), Check::Found); + assert_eq!(slot_history.check(20), Check::Found); + assert_eq!(slot_history.check(MAX_ENTRIES), 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); + assert_eq!(slot_history.check(0), Check::TooOld); + assert_eq!(slot_history.check(1), Check::TooOld); + assert_eq!(slot_history.check(2), Check::TooOld); + assert_eq!(slot_history.check(20), Check::TooOld); + assert_eq!(slot_history.check(21), Check::TooOld); + assert_eq!(slot_history.check(MAX_ENTRIES), 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); } }