diff --git a/Cargo.lock b/Cargo.lock index 8f8e9a02c..83fcb4123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3930,6 +3930,7 @@ dependencies = [ "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 9cc40a6ab..8529bea56 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1830,6 +1830,7 @@ dependencies = [ "assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "ed25519-dalek 1.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index dc45a1ecc..90b573baf 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -227,9 +227,9 @@ mod tests { .iter() .map(|meta| { if sysvar::clock::check_id(&meta.pubkey) { - sysvar::clock::Clock::default().create_account(1) + Clock::default().create_account(1) } else if sysvar::slot_hashes::check_id(&meta.pubkey) { - sysvar::slot_hashes::create_account(1, &[]) + SlotHashes::default().create_account(1) } else if sysvar::rent::check_id(&meta.pubkey) { Rent::free().create_account(1) } else { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d5f8e90d9..2428c17cb 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -40,6 +40,7 @@ use solana_sdk::{ pubkey::Pubkey, signature::{Keypair, Signature}, slot_hashes::SlotHashes, + slot_history::SlotHistory, system_transaction, sysvar::{self, Sysvar}, timing::years_as_slots, @@ -516,16 +517,26 @@ impl Bank { ); } + fn update_slot_history(&self) { + let mut slot_history = self + .get_account(&sysvar::slot_history::id()) + .map(|account| SlotHistory::from_account(&account).unwrap()) + .unwrap_or_default(); + + slot_history.add(self.slot()); + + self.store_account(&sysvar::slot_history::id(), &slot_history.create_account(1)); + } + fn update_slot_hashes(&self) { - let mut account = self + let mut slot_hashes = self .get_account(&sysvar::slot_hashes::id()) - .unwrap_or_else(|| sysvar::slot_hashes::create_account(1, &[])); + .map(|account| SlotHashes::from_account(&account).unwrap()) + .unwrap_or_default(); - let mut slot_hashes = SlotHashes::from_account(&account).unwrap(); slot_hashes.add(self.slot(), self.hash()); - slot_hashes.to_account(&mut account).unwrap(); - self.store_account(&sysvar::slot_hashes::id(), &account); + self.store_account(&sysvar::slot_hashes::id(), &slot_hashes.create_account(1)); } fn update_fees(&self) { @@ -650,6 +661,7 @@ impl Bank { // finish up any deferred changes to account state self.collect_fees(); self.distribute_rent(); + self.update_slot_history(); // freeze is a one-way trip, idempotent *hash = self.hash_internal_state(); diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 47473eb83..b10c645dd 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -28,6 +28,7 @@ default = [ assert_matches = { version = "1.3.0", optional = true } bincode = "1.2.1" bs58 = "0.3.0" +bv = { version = "0.11.0", features = ["serde"] } byteorder = { version = "1.3.2", optional = true } generic-array = { version = "0.13.2", default-features = false, features = ["serde", "more_lengths"] } hex = "0.4.0" diff --git a/sdk/benches/slot_hashes.rs b/sdk/benches/slot_hashes.rs index be4b94cb8..9affd45df 100644 --- a/sdk/benches/slot_hashes.rs +++ b/sdk/benches/slot_hashes.rs @@ -3,7 +3,7 @@ extern crate test; use solana_sdk::{ hash::Hash, - slot_hashes::{Slot, SlotHashes, MAX_SLOT_HASHES}, + slot_hashes::{Slot, SlotHashes, MAX_ENTRIES}, sysvar::Sysvar, }; use test::Bencher; @@ -11,12 +11,10 @@ use test::Bencher; #[bench] fn bench_to_from_account(b: &mut Bencher) { let mut slot_hashes = SlotHashes::new(&[]); - for i in 0..MAX_SLOT_HASHES { + for i in 0..MAX_ENTRIES { slot_hashes.add(i as Slot, Hash::default()); } - let mut reps = 0; b.iter(|| { - reps += 1; let account = slot_hashes.create_account(0); slot_hashes = SlotHashes::from_account(&account).unwrap(); }); diff --git a/sdk/benches/slot_history.rs b/sdk/benches/slot_history.rs new file mode 100644 index 000000000..6a4ac824b --- /dev/null +++ b/sdk/benches/slot_history.rs @@ -0,0 +1,15 @@ +#![feature(test)] + +extern crate test; +use solana_sdk::{slot_history::SlotHistory, sysvar::Sysvar}; +use test::Bencher; + +#[bench] +fn bench_to_from_account(b: &mut Bencher) { + let mut slot_history = SlotHistory::default(); + + b.iter(|| { + let account = slot_history.create_account(0); + slot_history = SlotHistory::from_account(&account).unwrap(); + }); +} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index d2de4d0af..d81d9e2c5 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -26,6 +26,7 @@ pub mod rent; pub mod rpc_port; pub mod short_vec; pub mod slot_hashes; +pub mod slot_history; pub mod system_instruction; pub mod system_program; pub mod sysvar; diff --git a/sdk/src/slot_hashes.rs b/sdk/src/slot_hashes.rs index 0a0419e56..7a4df5fb6 100644 --- a/sdk/src/slot_hashes.rs +++ b/sdk/src/slot_hashes.rs @@ -5,7 +5,7 @@ use crate::hash::Hash; use std::{iter::FromIterator, ops::Deref}; -pub const MAX_SLOT_HASHES: usize = 512; // about 2.5 minutes to get your vote in +pub const MAX_ENTRIES: usize = 512; // about 2.5 minutes to get your vote in pub use crate::clock::Slot; @@ -21,7 +21,7 @@ impl SlotHashes { Ok(index) => (self.0)[index] = (slot, hash), Err(index) => (self.0).insert(index, (slot, hash)), } - (self.0).truncate(MAX_SLOT_HASHES); + (self.0).truncate(MAX_ENTRIES); } #[allow(clippy::trivially_copy_pass_by_ref)] pub fn get(&self, slot: &Slot) -> Option<&Hash> { @@ -68,16 +68,16 @@ mod tests { ); let mut slot_hashes = SlotHashes::new(&[]); - for i in 0..MAX_SLOT_HASHES + 1 { + 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_SLOT_HASHES { - assert_eq!(slot_hashes[i].0, (MAX_SLOT_HASHES - i) as u64); + for i in 0..MAX_ENTRIES { + assert_eq!(slot_hashes[i].0, (MAX_ENTRIES - i) as u64); } - assert_eq!(slot_hashes.len(), MAX_SLOT_HASHES); + assert_eq!(slot_hashes.len(), MAX_ENTRIES); } } diff --git a/sdk/src/slot_history.rs b/sdk/src/slot_history.rs new file mode 100644 index 000000000..c92e07758 --- /dev/null +++ b/sdk/src/slot_history.rs @@ -0,0 +1,72 @@ +//! +//! slot history +//! +pub use crate::clock::Slot; +use bv::BitVec; + +#[repr(C)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct SlotHistory { + pub bits: BitVec, + 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 } + } +} + +pub const MAX_ENTRIES: u64 = 1024 * 1024; // 1 million slots is about 5 days + +#[derive(PartialEq, Debug)] +pub enum Check { + Future, + TooOld, + Found, + NotFound, +} + +impl SlotHistory { + pub fn add(&mut self, slot: Slot) { + 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.next_slot { + Check::Future + } else if self.next_slot - slot > MAX_ENTRIES { + Check::TooOld + } else if self.bits.get(slot % MAX_ENTRIES) { + Check::Found + } else { + Check::NotFound + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test() { + let mut slot_history = SlotHistory::default(); + 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); + } + 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); + } +} diff --git a/sdk/src/sysvar/mod.rs b/sdk/src/sysvar/mod.rs index 0c19f2ba2..8c6ba3164 100644 --- a/sdk/src/sysvar/mod.rs +++ b/sdk/src/sysvar/mod.rs @@ -14,6 +14,7 @@ pub mod recent_blockhashes; pub mod rent; pub mod rewards; pub mod slot_hashes; +pub mod slot_history; pub mod stake_history; pub fn is_sysvar_id(id: &Pubkey) -> bool { @@ -24,6 +25,7 @@ pub fn is_sysvar_id(id: &Pubkey) -> bool { || rent::check_id(id) || rewards::check_id(id) || slot_hashes::check_id(id) + || slot_history::check_id(id) || stake_history::check_id(id) } @@ -59,11 +61,8 @@ pub trait SysvarId { pub trait Sysvar: SysvarId + Default + Sized + serde::Serialize + serde::de::DeserializeOwned { - fn biggest() -> Self { - Self::default() - } fn size_of() -> usize { - bincode::serialized_size(&Self::biggest()).unwrap() as usize + bincode::serialized_size(&Self::default()).unwrap() as usize } fn from_account(account: &Account) -> Option { bincode::deserialize(&account.data).ok() @@ -84,7 +83,8 @@ pub trait Sysvar: Self::from_account(account.account).ok_or(InstructionError::InvalidArgument) } fn create_account(&self, lamports: u64) -> Account { - let mut account = Account::new(lamports, Self::size_of(), &id()); + let data_len = Self::size_of().max(bincode::serialized_size(self).unwrap() as usize); + let mut account = Account::new(lamports, data_len, &id()); self.to_account(&mut account).unwrap(); account } diff --git a/sdk/src/sysvar/recent_blockhashes.rs b/sdk/src/sysvar/recent_blockhashes.rs index 89ed5cd40..1f2feaef7 100644 --- a/sdk/src/sysvar/recent_blockhashes.rs +++ b/sdk/src/sysvar/recent_blockhashes.rs @@ -39,8 +39,9 @@ impl<'a> FromIterator<&'a Hash> for RecentBlockhashes { } impl Sysvar for RecentBlockhashes { - fn biggest() -> Self { - RecentBlockhashes(vec![Hash::default(); MAX_ENTRIES]) + fn size_of() -> usize { + // hard-coded so that we don't have to construct an empty + 1032 // golden, update if MAX_ENTRIES changes } } @@ -86,6 +87,15 @@ mod tests { use super::*; use crate::hash::Hash; + #[test] + fn test_size_of() { + assert_eq!( + bincode::serialized_size(&RecentBlockhashes(vec![Hash::default(); MAX_ENTRIES])) + .unwrap() as usize, + RecentBlockhashes::size_of() + ); + } + #[test] fn test_create_account_empty() { let account = create_account_with_data(42, vec![].into_iter()); diff --git a/sdk/src/sysvar/slot_hashes.rs b/sdk/src/sysvar/slot_hashes.rs index 7bab4b31e..b21941214 100644 --- a/sdk/src/sysvar/slot_hashes.rs +++ b/sdk/src/sysvar/slot_hashes.rs @@ -2,32 +2,42 @@ //! //! this account carries the Bank's most recent blockhashes for some N parents //! -pub use crate::slot_hashes::{Slot, SlotHash, SlotHashes, MAX_SLOT_HASHES}; -use crate::{account::Account, hash::Hash, sysvar::Sysvar}; +pub use crate::slot_hashes::SlotHashes; + +use crate::sysvar::Sysvar; crate::declare_sysvar_id!("SysvarS1otHashes111111111111111111111111111", SlotHashes); impl Sysvar for SlotHashes { - fn biggest() -> Self { - // override - (0..MAX_SLOT_HASHES) - .map(|slot| (slot as Slot, Hash::default())) - .collect::() + // override + fn size_of() -> usize { + // hard-coded so that we don't have to construct an empty + 20_488 // golden, update if MAX_ENTRIES changes } } -pub fn create_account(lamports: u64, slot_hashes: &[SlotHash]) -> Account { - SlotHashes::new(slot_hashes).create_account(lamports) -} - #[cfg(test)] mod tests { use super::*; + use crate::{clock::Slot, hash::Hash, slot_hashes::MAX_ENTRIES}; + + #[test] + fn test_size_of() { + assert_eq!( + SlotHashes::size_of(), + bincode::serialized_size( + &(0..MAX_ENTRIES) + .map(|slot| (slot as Slot, Hash::default())) + .collect::() + ) + .unwrap() as usize + ); + } #[test] fn test_create_account() { let lamports = 42; - let account = create_account(lamports, &[]); + let account = SlotHashes::new(&[]).create_account(lamports); assert_eq!(account.data.len(), SlotHashes::size_of()); let slot_hashes = SlotHashes::from_account(&account); assert_eq!(slot_hashes, Some(SlotHashes::default())); diff --git a/sdk/src/sysvar/slot_history.rs b/sdk/src/sysvar/slot_history.rs new file mode 100644 index 000000000..7aafa02d8 --- /dev/null +++ b/sdk/src/sysvar/slot_history.rs @@ -0,0 +1,30 @@ +//! named accounts for synthesized data accounts for bank state, etc. +//! +//! this account carries a bitvector of slots present over the past +//! epoch +//! +pub use crate::slot_history::SlotHistory; + +use crate::sysvar::Sysvar; + +crate::declare_sysvar_id!("SysvarS1otHistory11111111111111111111111111", SlotHistory); + +impl Sysvar for SlotHistory { + // override + fn size_of() -> usize { + // hard-coded so that we don't have to construct an empty + 131_097 // golden, update if MAX_ENTRIES changes + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_size_of() { + assert_eq!( + SlotHistory::size_of(), + bincode::serialized_size(&SlotHistory::default()).unwrap() as usize + ); + } +} diff --git a/sdk/src/sysvar/stake_history.rs b/sdk/src/sysvar/stake_history.rs index 917c42ba8..5399fcfe0 100644 --- a/sdk/src/sysvar/stake_history.rs +++ b/sdk/src/sysvar/stake_history.rs @@ -9,7 +9,7 @@ use std::ops::Deref; crate::declare_sysvar_id!("SysvarStakeHistory1111111111111111111111111", StakeHistory); -pub const MAX_STAKE_HISTORY: usize = 512; // it should never take as many as 512 epochs to warm up or cool down +pub const MAX_ENTRIES: usize = 512; // it should never take as many as 512 epochs to warm up or cool down #[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)] pub struct StakeHistoryEntry { @@ -23,8 +23,10 @@ pub struct StakeHistoryEntry { pub struct StakeHistory(Vec<(Epoch, StakeHistoryEntry)>); impl Sysvar for StakeHistory { - fn biggest() -> Self { - StakeHistory(vec![(0, StakeHistoryEntry::default()); MAX_STAKE_HISTORY]) + // override + fn size_of() -> usize { + // hard-coded so that we don't have to construct an empty + 16392 // golden, update if MAX_ENTRIES changes } } @@ -41,7 +43,7 @@ impl StakeHistory { Ok(index) => (self.0)[index] = (epoch, entry), Err(index) => (self.0).insert(index, (epoch, entry)), } - (self.0).truncate(MAX_STAKE_HISTORY); + (self.0).truncate(MAX_ENTRIES); } } @@ -56,30 +58,33 @@ pub fn create_account(lamports: u64, stake_history: &StakeHistory) -> Account { stake_history.create_account(lamports) } -use crate::account::KeyedAccount; -use crate::instruction::InstructionError; -pub fn from_keyed_account(account: &KeyedAccount) -> Result { - if !check_id(account.unsigned_key()) { - return Err(InstructionError::InvalidArgument); - } - StakeHistory::from_account(account.account).ok_or(InstructionError::InvalidArgument) -} - #[cfg(test)] mod tests { use super::*; + #[test] + fn test_size_of() { + assert_eq!( + bincode::serialized_size(&StakeHistory(vec![ + (0, StakeHistoryEntry::default()); + MAX_ENTRIES + ])) + .unwrap() as usize, + StakeHistory::size_of() + ); + } + #[test] fn test_create_account() { let lamports = 42; - let account = create_account(lamports, &StakeHistory::default()); + let account = StakeHistory::default().create_account(lamports); assert_eq!(account.data.len(), StakeHistory::size_of()); let stake_history = StakeHistory::from_account(&account); assert_eq!(stake_history, Some(StakeHistory::default())); let mut stake_history = stake_history.unwrap(); - for i in 0..MAX_STAKE_HISTORY as u64 + 1 { + for i in 0..MAX_ENTRIES as u64 + 1 { stake_history.add( i, StakeHistoryEntry { @@ -88,7 +93,7 @@ mod tests { }, ); } - assert_eq!(stake_history.len(), MAX_STAKE_HISTORY); + assert_eq!(stake_history.len(), MAX_ENTRIES); assert_eq!(stake_history.iter().map(|entry| entry.0).min().unwrap(), 1); assert_eq!(stake_history.get(&0), None); assert_eq!( @@ -100,7 +105,7 @@ mod tests { ); // verify the account can hold a full instance assert_eq!( - StakeHistory::from_account(&create_account(lamports, &stake_history)), + StakeHistory::from_account(&stake_history.create_account(lamports)), Some(stake_history) ); }