From 38a3d0803341f35e16cf1b11c77667420d972eed Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 31 Oct 2019 11:34:32 -0600 Subject: [PATCH] SDK: Add sysvar to expose recent block hashes to programs --- runtime/src/bank.rs | 34 +++++++++++ runtime/src/blockhash_queue.rs | 34 +++++++++++ sdk/src/sysvar/mod.rs | 2 + sdk/src/sysvar/recent_block_hashes.rs | 81 +++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 sdk/src/sysvar/recent_block_hashes.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 22e9be0a6d..9d9e1cec31 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -288,6 +288,7 @@ impl Bank { bank.update_clock(); bank.update_rent(); bank.update_epoch_schedule(); + bank.update_recent_block_hashes(); bank } @@ -381,6 +382,7 @@ impl Bank { new.update_stake_history(Some(parent.epoch())); new.update_clock(); new.update_fees(); + new.update_recent_block_hashes(); new } @@ -535,6 +537,18 @@ impl Bank { ); } + fn update_recent_block_hashes(&self) { + let recent_block_hashes = self + .blockhash_queue + .read() + .unwrap() + .get_recent_blockhashes(sysvar::recent_block_hashes::MAX_ENTRIES); + self.store_account( + &sysvar::recent_block_hashes::id(), + &sysvar::recent_block_hashes::create_account(1, recent_block_hashes), + ); + } + // If the point values are not `normal`, bring them back into range and // set them to the last value or 0. fn check_point_values( @@ -3341,4 +3355,24 @@ mod tests { // Non-native loader accounts can not be used for instruction processing bank.add_instruction_processor(mint_keypair.pubkey(), mock_ix_processor); } + + #[test] + fn test_recent_block_hashes_sysvar() { + let (genesis_block, _mint_keypair) = create_genesis_block(500); + let mut bank = Arc::new(Bank::new(&genesis_block)); + let bhq_account = bank + .get_account(&sysvar::recent_block_hashes::id()) + .unwrap(); + let recent_block_hashes = + sysvar::recent_block_hashes::RecentBlockHashes::from_account(&bhq_account).unwrap(); + assert_eq!(recent_block_hashes.len(), 1); + goto_end_of_slot(Arc::get_mut(&mut bank).unwrap()); + let bank = Arc::new(new_from_parent(&bank)); + let bhq_account = bank + .get_account(&sysvar::recent_block_hashes::id()) + .unwrap(); + let recent_block_hashes = + sysvar::recent_block_hashes::RecentBlockHashes::from_account(&bhq_account).unwrap(); + assert_eq!(recent_block_hashes.len(), 2); + } } diff --git a/runtime/src/blockhash_queue.rs b/runtime/src/blockhash_queue.rs index 8c7bb34c7e..7c963413e1 100644 --- a/runtime/src/blockhash_queue.rs +++ b/runtime/src/blockhash_queue.rs @@ -114,6 +114,17 @@ impl BlockhashQueue { } None } + + pub fn get_recent_blockhashes(&self, count: usize) -> Vec { + let mut recent_blockhashes = self.ages.iter().collect::>(); + recent_blockhashes + .sort_by(|(_, v1), (_, v2)| v1.hash_height.cmp(&v2.hash_height).reverse()); + recent_blockhashes + .iter() + .take(count) + .map(|(k, _)| **k) + .collect() + } } #[cfg(test)] mod tests { @@ -150,4 +161,27 @@ mod tests { assert_eq!(last_hash, hash_queue.last_hash()); assert!(hash_queue.check_hash_age(&last_hash, 0)); } + + #[test] + fn test_get_recent_blockhashes() { + const RECENT_MAX: usize = 32; + const QUEUE_MAX: usize = 2 * RECENT_MAX; + let mut blockhash_queue = BlockhashQueue::new(QUEUE_MAX); + let recent_blockhashes = blockhash_queue.get_recent_blockhashes(RECENT_MAX); + // Sanity-check an empty BlockhashQueue + assert_eq!(recent_blockhashes.len() as u64, 0); + for i in 0..QUEUE_MAX { + let hash = hash(&serialize(&i).unwrap()); + blockhash_queue.register_hash(&hash, &FeeCalculator::default()); + } + let recent_blockhashes = blockhash_queue.get_recent_blockhashes(RECENT_MAX); + // Verify that we returned a truncated list + assert!((recent_blockhashes.len() as u64) < blockhash_queue.hash_height()); + // Verify that the truncation occurred at the intended size + assert_eq!(recent_blockhashes.len(), RECENT_MAX); + // Verify that the returned hashes are most recent + for hash in &recent_blockhashes { + assert!(blockhash_queue.check_hash_age(hash, RECENT_MAX)); + } + } } diff --git a/sdk/src/sysvar/mod.rs b/sdk/src/sysvar/mod.rs index 9686ac8be6..ab95c6da5b 100644 --- a/sdk/src/sysvar/mod.rs +++ b/sdk/src/sysvar/mod.rs @@ -5,6 +5,7 @@ use crate::pubkey::Pubkey; pub mod clock; pub mod epoch_schedule; pub mod fees; +pub mod recent_block_hashes; pub mod rent; pub mod rewards; pub mod slot_hashes; @@ -14,6 +15,7 @@ pub fn is_sysvar_id(id: &Pubkey) -> bool { clock::check_id(id) || epoch_schedule::check_id(id) || fees::check_id(id) + || recent_block_hashes::check_id(id) || rent::check_id(id) || rewards::check_id(id) || slot_hashes::check_id(id) diff --git a/sdk/src/sysvar/recent_block_hashes.rs b/sdk/src/sysvar/recent_block_hashes.rs new file mode 100644 index 0000000000..d8288cacde --- /dev/null +++ b/sdk/src/sysvar/recent_block_hashes.rs @@ -0,0 +1,81 @@ +use crate::{account::Account, account_info::AccountInfo, hash::Hash, sysvar}; +use bincode::serialized_size; +use std::ops::Deref; + +pub const MAX_ENTRIES: usize = 32; +const ID: [u8; 32] = [ + 0x06, 0xa7, 0xd5, 0x17, 0x19, 0x2c, 0x56, 0x8e, 0xe0, 0x8a, 0x84, 0x5f, 0x73, 0xd2, 0x97, 0x88, + 0xcf, 0x03, 0x5c, 0x31, 0x45, 0xb2, 0x1a, 0xb3, 0x44, 0xd8, 0x06, 0x2e, 0xa9, 0x40, 0x00, 0x00, +]; + +crate::solana_sysvar_id!(ID, "SysvarRecentB1ockHashes11111111111111111111"); + +#[repr(C)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct RecentBlockHashes(Vec); + +impl Default for RecentBlockHashes { + fn default() -> Self { + Self(Vec::with_capacity(MAX_ENTRIES)) + } +} + +impl RecentBlockHashes { + pub fn from_account(account: &Account) -> Option { + account.deserialize_data().ok() + } + pub fn to_account(&self, account: &mut Account) -> Option<()> { + account.serialize_data(self).unwrap(); + Some(()) + } + pub fn from_account_info(account: &AccountInfo) -> Option { + account.deserialize_data().ok() + } + pub fn to_account_info(&self, account: &mut AccountInfo) -> Option<()> { + account.serialize_data(self).ok() + } + pub fn size_of() -> usize { + serialized_size(&RecentBlockHashes(vec![Hash::default(); MAX_ENTRIES])).unwrap() as usize + } +} + +impl Deref for RecentBlockHashes { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub fn create_account(lamports: u64, recent_block_hashes: Vec) -> Account { + let mut account = Account::new(lamports, RecentBlockHashes::size_of(), &sysvar::id()); + let recent_block_hashes = RecentBlockHashes(recent_block_hashes); + recent_block_hashes.to_account(&mut account).unwrap(); + account +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::Hash; + + #[test] + fn test_create_account_empty() { + let account = create_account(42, vec![]); + let recent_block_hashes = RecentBlockHashes::from_account(&account).unwrap(); + assert_eq!(recent_block_hashes, RecentBlockHashes::default()); + } + + #[test] + fn test_create_account_full() { + let account = create_account(42, vec![Hash::default(); MAX_ENTRIES]); + let recent_block_hashes = RecentBlockHashes::from_account(&account).unwrap(); + assert_eq!(recent_block_hashes.len(), MAX_ENTRIES); + } + + #[test] + #[should_panic] + fn test_create_account_too_big() { + let account = create_account(42, vec![Hash::default(); MAX_ENTRIES + 1]); + RecentBlockHashes::from_account(&account).unwrap(); + } +}