From 38a3d0803341f35e16cf1b11c77667420d972eed Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 31 Oct 2019 11:34:32 -0600 Subject: [PATCH 1/9] 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 22e9be0a6d1aff..9d9e1cec31f05c 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 8c7bb34c7eaaa7..7c963413e1ad42 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 9686ac8be62821..ab95c6da5b4a67 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 00000000000000..d8288cacde7972 --- /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(); + } +} From 054e1fa7df51ab89ace3a1338949edf79e7666db Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 31 Oct 2019 14:48:44 -0600 Subject: [PATCH 2/9] Blockhashes is one word --- runtime/src/bank.rs | 32 +++++++++++++-------------- sdk/src/sysvar/mod.rs | 4 ++-- sdk/src/sysvar/recent_block_hashes.rs | 28 +++++++++++------------ 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 9d9e1cec31f05c..ab7793319aabab 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -288,7 +288,7 @@ impl Bank { bank.update_clock(); bank.update_rent(); bank.update_epoch_schedule(); - bank.update_recent_block_hashes(); + bank.update_recent_blockhashes(); bank } @@ -382,7 +382,7 @@ impl Bank { new.update_stake_history(Some(parent.epoch())); new.update_clock(); new.update_fees(); - new.update_recent_block_hashes(); + new.update_recent_blockhashes(); new } @@ -537,15 +537,15 @@ impl Bank { ); } - fn update_recent_block_hashes(&self) { - let recent_block_hashes = self + fn update_recent_blockhashes(&self) { + let recent_blockhashes = self .blockhash_queue .read() .unwrap() - .get_recent_blockhashes(sysvar::recent_block_hashes::MAX_ENTRIES); + .get_recent_blockhashes(sysvar::recent_blockhashes::MAX_ENTRIES); self.store_account( - &sysvar::recent_block_hashes::id(), - &sysvar::recent_block_hashes::create_account(1, recent_block_hashes), + &sysvar::recent_blockhashes::id(), + &sysvar::recent_blockhashes::create_account(1, recent_blockhashes), ); } @@ -3357,22 +3357,22 @@ mod tests { } #[test] - fn test_recent_block_hashes_sysvar() { + fn test_recent_blockhashes_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()) + .get_account(&sysvar::recent_blockhashes::id()) .unwrap(); - let recent_block_hashes = - sysvar::recent_block_hashes::RecentBlockHashes::from_account(&bhq_account).unwrap(); - assert_eq!(recent_block_hashes.len(), 1); + let recent_blockhashes = + sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); + assert_eq!(recent_blockhashes.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()) + .get_account(&sysvar::recent_blockhashes::id()) .unwrap(); - let recent_block_hashes = - sysvar::recent_block_hashes::RecentBlockHashes::from_account(&bhq_account).unwrap(); - assert_eq!(recent_block_hashes.len(), 2); + let recent_blockhashes = + sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); + assert_eq!(recent_blockhashes.len(), 2); } } diff --git a/sdk/src/sysvar/mod.rs b/sdk/src/sysvar/mod.rs index ab95c6da5b4a67..231786d3d4470e 100644 --- a/sdk/src/sysvar/mod.rs +++ b/sdk/src/sysvar/mod.rs @@ -5,7 +5,7 @@ use crate::pubkey::Pubkey; pub mod clock; pub mod epoch_schedule; pub mod fees; -pub mod recent_block_hashes; +pub mod recent_blockhashes; pub mod rent; pub mod rewards; pub mod slot_hashes; @@ -15,7 +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) + || recent_blockhashes::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 index d8288cacde7972..1a8742592d919c 100644 --- a/sdk/src/sysvar/recent_block_hashes.rs +++ b/sdk/src/sysvar/recent_block_hashes.rs @@ -12,15 +12,15 @@ crate::solana_sysvar_id!(ID, "SysvarRecentB1ockHashes11111111111111111111"); #[repr(C)] #[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct RecentBlockHashes(Vec); +pub struct RecentBlockhashes(Vec); -impl Default for RecentBlockHashes { +impl Default for RecentBlockhashes { fn default() -> Self { Self(Vec::with_capacity(MAX_ENTRIES)) } } -impl RecentBlockHashes { +impl RecentBlockhashes { pub fn from_account(account: &Account) -> Option { account.deserialize_data().ok() } @@ -35,21 +35,21 @@ impl RecentBlockHashes { account.serialize_data(self).ok() } pub fn size_of() -> usize { - serialized_size(&RecentBlockHashes(vec![Hash::default(); MAX_ENTRIES])).unwrap() as usize + serialized_size(&RecentBlockhashes(vec![Hash::default(); MAX_ENTRIES])).unwrap() as usize } } -impl Deref for RecentBlockHashes { +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(); +pub fn create_account(lamports: u64, recent_blockhashes: Vec) -> Account { + let mut account = Account::new(lamports, RecentBlockhashes::size_of(), &sysvar::id()); + let recent_blockhashes = RecentBlockhashes(recent_blockhashes); + recent_blockhashes.to_account(&mut account).unwrap(); account } @@ -61,21 +61,21 @@ mod tests { #[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()); + let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); + assert_eq!(recent_blockhashes, 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); + let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); + assert_eq!(recent_blockhashes.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(); + RecentBlockhashes::from_account(&account).unwrap(); } } From 5211e46ad9f30edd64f32c07c8a4463cc5363c5e Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 31 Oct 2019 15:31:53 -0600 Subject: [PATCH 3/9] Missed one --- sdk/src/sysvar/{recent_block_hashes.rs => recent_blockhashes.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sdk/src/sysvar/{recent_block_hashes.rs => recent_blockhashes.rs} (100%) diff --git a/sdk/src/sysvar/recent_block_hashes.rs b/sdk/src/sysvar/recent_blockhashes.rs similarity index 100% rename from sdk/src/sysvar/recent_block_hashes.rs rename to sdk/src/sysvar/recent_blockhashes.rs From 486f01790c271cf09ac3dbffba0e40e526e81324 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Thu, 31 Oct 2019 16:19:49 -0600 Subject: [PATCH 4/9] Avoid allocs on update --- runtime/src/bank.rs | 19 +++++++++---------- sdk/src/sysvar/recent_blockhashes.rs | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index ab7793319aabab..318017e2bb2618 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -543,10 +543,13 @@ impl Bank { .read() .unwrap() .get_recent_blockhashes(sysvar::recent_blockhashes::MAX_ENTRIES); - self.store_account( - &sysvar::recent_blockhashes::id(), - &sysvar::recent_blockhashes::create_account(1, recent_blockhashes), - ); + let id = sysvar::recent_blockhashes::id(); + let mut account = self + .get_account(&id) + .or_else(|| Some(sysvar::recent_blockhashes::create_account(1))) + .unwrap(); + sysvar::recent_blockhashes::update_account(&mut account, recent_blockhashes).unwrap(); + self.store_account(&id, &account); } // If the point values are not `normal`, bring them back into range and @@ -3360,17 +3363,13 @@ mod tests { fn test_recent_blockhashes_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_blockhashes::id()) - .unwrap(); + let bhq_account = bank.get_account(&sysvar::recent_blockhashes::id()).unwrap(); let recent_blockhashes = sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); assert_eq!(recent_blockhashes.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_blockhashes::id()) - .unwrap(); + let bhq_account = bank.get_account(&sysvar::recent_blockhashes::id()).unwrap(); let recent_blockhashes = sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); assert_eq!(recent_blockhashes.len(), 2); diff --git a/sdk/src/sysvar/recent_blockhashes.rs b/sdk/src/sysvar/recent_blockhashes.rs index 1a8742592d919c..e3902b29560e3f 100644 --- a/sdk/src/sysvar/recent_blockhashes.rs +++ b/sdk/src/sysvar/recent_blockhashes.rs @@ -46,10 +46,18 @@ impl Deref for RecentBlockhashes { } } -pub fn create_account(lamports: u64, recent_blockhashes: Vec) -> Account { - let mut account = Account::new(lamports, RecentBlockhashes::size_of(), &sysvar::id()); +pub fn create_account(lamports: u64) -> Account { + Account::new(lamports, RecentBlockhashes::size_of(), &sysvar::id()) +} + +pub fn update_account(account: &mut Account, recent_blockhashes: Vec) -> Option<()> { let recent_blockhashes = RecentBlockhashes(recent_blockhashes); - recent_blockhashes.to_account(&mut account).unwrap(); + recent_blockhashes.to_account(account) +} + +pub fn create_account_with_data(lamports: u64, recent_blockhashes: Vec) -> Account { + let mut account = create_account(lamports); + update_account(&mut account, recent_blockhashes).unwrap(); account } @@ -60,14 +68,14 @@ mod tests { #[test] fn test_create_account_empty() { - let account = create_account(42, vec![]); + let account = create_account_with_data(42, vec![]); let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); assert_eq!(recent_blockhashes, RecentBlockhashes::default()); } #[test] fn test_create_account_full() { - let account = create_account(42, vec![Hash::default(); MAX_ENTRIES]); + let account = create_account_with_data(42, vec![Hash::default(); MAX_ENTRIES]); let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); assert_eq!(recent_blockhashes.len(), MAX_ENTRIES); } @@ -75,7 +83,7 @@ mod tests { #[test] #[should_panic] fn test_create_account_too_big() { - let account = create_account(42, vec![Hash::default(); MAX_ENTRIES + 1]); + let account = create_account_with_data(42, vec![Hash::default(); MAX_ENTRIES + 1]); RecentBlockhashes::from_account(&account).unwrap(); } } From a8f8c3bfbe0d88e2229e67a29e9d2b489b93ff62 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Sat, 2 Nov 2019 10:40:14 -0600 Subject: [PATCH 5/9] unwrap_or_else --- runtime/src/bank.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 318017e2bb2618..897a40a6eac635 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -546,8 +546,7 @@ impl Bank { let id = sysvar::recent_blockhashes::id(); let mut account = self .get_account(&id) - .or_else(|| Some(sysvar::recent_blockhashes::create_account(1))) - .unwrap(); + .unwrap_or_else(|| sysvar::recent_blockhashes::create_account(1)); sysvar::recent_blockhashes::update_account(&mut account, recent_blockhashes).unwrap(); self.store_account(&id, &account); } From e83a32101bc8e93f1230ea8c13d6f58f2ecebb39 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Sat, 2 Nov 2019 11:28:10 -0600 Subject: [PATCH 6/9] Use iterators --- runtime/src/bank.rs | 32 +++++++++--------- runtime/src/blockhash_queue.rs | 32 ++++++------------ sdk/src/sysvar/recent_blockhashes.rs | 49 +++++++++++++++++++++------- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 897a40a6eac635..a5af71496c2858 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -538,16 +538,14 @@ impl Bank { } fn update_recent_blockhashes(&self) { - let recent_blockhashes = self - .blockhash_queue - .read() - .unwrap() - .get_recent_blockhashes(sysvar::recent_blockhashes::MAX_ENTRIES); let id = sysvar::recent_blockhashes::id(); let mut account = self .get_account(&id) .unwrap_or_else(|| sysvar::recent_blockhashes::create_account(1)); - sysvar::recent_blockhashes::update_account(&mut account, recent_blockhashes).unwrap(); + let blockhash_queue = self.blockhash_queue.read().unwrap(); + let recent_blockhash_iter = blockhash_queue.get_recent_blockhashes(); + sysvar::recent_blockhashes::update_account(&mut account, recent_blockhash_iter).unwrap(); + drop(blockhash_queue); self.store_account(&id, &account); } @@ -3362,15 +3360,17 @@ mod tests { fn test_recent_blockhashes_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_blockhashes::id()).unwrap(); - let recent_blockhashes = - sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); - assert_eq!(recent_blockhashes.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_blockhashes::id()).unwrap(); - let recent_blockhashes = - sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); - assert_eq!(recent_blockhashes.len(), 2); + for i in 1..5 { + let bhq_account = bank.get_account(&sysvar::recent_blockhashes::id()).unwrap(); + let recent_blockhashes = + sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); + // Check length + assert_eq!(recent_blockhashes.len(), i); + let most_recent_hash = recent_blockhashes.iter().nth(0).unwrap(); + // Check order + assert!(bank.check_hash_age(most_recent_hash, 0)); + goto_end_of_slot(Arc::get_mut(&mut bank).unwrap()); + bank = Arc::new(new_from_parent(&bank)); + } } } diff --git a/runtime/src/blockhash_queue.rs b/runtime/src/blockhash_queue.rs index 7c963413e1ad42..09a9a1a3f30589 100644 --- a/runtime/src/blockhash_queue.rs +++ b/runtime/src/blockhash_queue.rs @@ -115,21 +115,15 @@ 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() + pub fn get_recent_blockhashes(&self) -> impl Iterator { + (&self.ages).iter().map(|(k, v)| (v.hash_height, k)) } } #[cfg(test)] mod tests { use super::*; use bincode::serialize; + use solana_sdk::clock::MAX_RECENT_BLOCKHASHES; use solana_sdk::hash::hash; #[test] @@ -164,24 +158,18 @@ mod tests { #[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); + let mut blockhash_queue = BlockhashQueue::new(MAX_RECENT_BLOCKHASHES); + let recent_blockhashes = blockhash_queue.get_recent_blockhashes(); // Sanity-check an empty BlockhashQueue - assert_eq!(recent_blockhashes.len() as u64, 0); - for i in 0..QUEUE_MAX { + assert_eq!(recent_blockhashes.count(), 0); + for i in 0..MAX_RECENT_BLOCKHASHES { 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); + let recent_blockhashes = blockhash_queue.get_recent_blockhashes(); // Verify that the returned hashes are most recent - for hash in &recent_blockhashes { - assert!(blockhash_queue.check_hash_age(hash, RECENT_MAX)); + for (_slot, hash) in recent_blockhashes { + assert!(blockhash_queue.check_hash_age(hash, MAX_RECENT_BLOCKHASHES)); } } } diff --git a/sdk/src/sysvar/recent_blockhashes.rs b/sdk/src/sysvar/recent_blockhashes.rs index e3902b29560e3f..5df2e6383ae5a1 100644 --- a/sdk/src/sysvar/recent_blockhashes.rs +++ b/sdk/src/sysvar/recent_blockhashes.rs @@ -1,8 +1,10 @@ use crate::{account::Account, account_info::AccountInfo, hash::Hash, sysvar}; use bincode::serialized_size; +use std::collections::BinaryHeap; +use std::iter::FromIterator; use std::ops::Deref; -pub const MAX_ENTRIES: usize = 32; +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, @@ -20,6 +22,19 @@ impl Default for RecentBlockhashes { } } +impl<'a> FromIterator<&'a Hash> for RecentBlockhashes { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut new = Self::default(); + for i in iter { + new.0.push(*i) + } + new + } +} + impl RecentBlockhashes { pub fn from_account(account: &Account) -> Option { account.deserialize_data().ok() @@ -50,14 +65,22 @@ pub fn create_account(lamports: u64) -> Account { Account::new(lamports, RecentBlockhashes::size_of(), &sysvar::id()) } -pub fn update_account(account: &mut Account, recent_blockhashes: Vec) -> Option<()> { - let recent_blockhashes = RecentBlockhashes(recent_blockhashes); +pub fn update_account<'a, I>(account: &mut Account, recent_blockhash_iter: I) -> Option<()> +where + I: IntoIterator, +{ + let sorted = BinaryHeap::from_iter(recent_blockhash_iter); + let recent_blockhash_iter = sorted.into_iter().take(MAX_ENTRIES).map(|(_, hash)| hash); + let recent_blockhashes = RecentBlockhashes::from_iter(recent_blockhash_iter); recent_blockhashes.to_account(account) } -pub fn create_account_with_data(lamports: u64, recent_blockhashes: Vec) -> Account { +pub fn create_account_with_data<'a, I>(lamports: u64, recent_blockhash_iter: I) -> Account +where + I: IntoIterator, +{ let mut account = create_account(lamports); - update_account(&mut account, recent_blockhashes).unwrap(); + update_account(&mut account, recent_blockhash_iter).unwrap(); account } @@ -68,22 +91,26 @@ mod tests { #[test] fn test_create_account_empty() { - let account = create_account_with_data(42, vec![]); + let account = create_account_with_data(42, vec![].into_iter()); let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); assert_eq!(recent_blockhashes, RecentBlockhashes::default()); } #[test] fn test_create_account_full() { - let account = create_account_with_data(42, vec![Hash::default(); MAX_ENTRIES]); + let def_hash = Hash::default(); + let account = + create_account_with_data(42, vec![(0u64, &def_hash); MAX_ENTRIES].into_iter()); let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); assert_eq!(recent_blockhashes.len(), MAX_ENTRIES); } #[test] - #[should_panic] - fn test_create_account_too_big() { - let account = create_account_with_data(42, vec![Hash::default(); MAX_ENTRIES + 1]); - RecentBlockhashes::from_account(&account).unwrap(); + fn test_create_account_truncate() { + let def_hash = Hash::default(); + let account = + create_account_with_data(42, vec![(0u64, &def_hash); MAX_ENTRIES + 1].into_iter()); + let recent_blockhashes = RecentBlockhashes::from_account(&account).unwrap(); + assert_eq!(recent_blockhashes.len(), MAX_ENTRIES); } } From 42c5a5fb261a4cdd45229c89b2bd1c2851f02fce Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Sat, 2 Nov 2019 13:13:45 -0600 Subject: [PATCH 7/9] Add microbench --- runtime/benches/bank.rs | 24 ++++++++++++++++++++++++ runtime/src/bank.rs | 26 +++++++++++++------------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/runtime/benches/bank.rs b/runtime/benches/bank.rs index db13f66e2258ef..42215c31d67f52 100644 --- a/runtime/benches/bank.rs +++ b/runtime/benches/bank.rs @@ -9,6 +9,7 @@ use solana_runtime::loader_utils::create_invoke_instruction; use solana_sdk::account::KeyedAccount; use solana_sdk::client::AsyncClient; use solana_sdk::client::SyncClient; +use solana_sdk::clock::MAX_RECENT_BLOCKHASHES; use solana_sdk::genesis_block::create_genesis_block; use solana_sdk::instruction::InstructionError; use solana_sdk::pubkey::Pubkey; @@ -173,3 +174,26 @@ fn bench_bank_async_process_builtin_transactions(bencher: &mut Bencher) { fn bench_bank_async_process_native_loader_transactions(bencher: &mut Bencher) { do_bench_transactions(bencher, &async_bencher, &create_native_loader_transactions); } + +#[bench] +#[ignore] +fn bench_bank_update_recent_blockhashes(bencher: &mut Bencher) { + let (genesis_block, _mint_keypair) = create_genesis_block(100); + let mut bank = Arc::new(Bank::new(&genesis_block)); + goto_end_of_slot(Arc::get_mut(&mut bank).unwrap()); + let genesis_blockhash = bank.last_blockhash(); + // Prime blockhash_queue + for i in 0..(MAX_RECENT_BLOCKHASHES + 1) { + bank = Arc::new(Bank::new_from_parent( + &bank, + &Pubkey::default(), + (i + 1) as u64, + )); + goto_end_of_slot(Arc::get_mut(&mut bank).unwrap()); + } + // Verify blockhash_queue is full (genesis blockhash has been kicked out) + assert!(!bank.check_hash_age(&genesis_blockhash, MAX_RECENT_BLOCKHASHES)); + bencher.iter(|| { + bank.update_recent_blockhashes(); + }); +} diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index a5af71496c2858..626f6957b19598 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -537,7 +537,7 @@ impl Bank { ); } - fn update_recent_blockhashes(&self) { + pub fn update_recent_blockhashes(&self) { let id = sysvar::recent_blockhashes::id(); let mut account = self .get_account(&id) @@ -1632,6 +1632,18 @@ impl Drop for Bank { } } +pub fn goto_end_of_slot(bank: &mut Bank) { + let mut tick_hash = bank.last_blockhash(); + loop { + tick_hash = hashv(&[&tick_hash.as_ref(), &[42]]); + bank.register_tick(&tick_hash); + if tick_hash == bank.last_blockhash() { + bank.freeze(); + return; + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -2056,18 +2068,6 @@ mod tests { assert_eq!(bank.get_balance(&key.pubkey()), 1); } - fn goto_end_of_slot(bank: &mut Bank) { - let mut tick_hash = bank.last_blockhash(); - loop { - tick_hash = hashv(&[&tick_hash.as_ref(), &[42]]); - bank.register_tick(&tick_hash); - if tick_hash == bank.last_blockhash() { - bank.freeze(); - return; - } - } - } - #[test] fn test_bank_tx_fee() { let arbitrary_transfer_amount = 42; From 1c76a8723e9e28f4e18823e70e3068105f62de61 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Sat, 2 Nov 2019 13:22:17 -0600 Subject: [PATCH 8/9] Revert "unwrap_or_else" This reverts commit a8f8c3bfbe0d88e2229e67a29e9d2b489b93ff62. --- runtime/src/bank.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 626f6957b19598..ce2c70bb0bf691 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -541,7 +541,8 @@ impl Bank { let id = sysvar::recent_blockhashes::id(); let mut account = self .get_account(&id) - .unwrap_or_else(|| sysvar::recent_blockhashes::create_account(1)); + .or_else(|| Some(sysvar::recent_blockhashes::create_account(1))) + .unwrap(); let blockhash_queue = self.blockhash_queue.read().unwrap(); let recent_blockhash_iter = blockhash_queue.get_recent_blockhashes(); sysvar::recent_blockhashes::update_account(&mut account, recent_blockhash_iter).unwrap(); From 14b26b118c467b5a7eefcde088d30eca0e06bfb2 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Sat, 2 Nov 2019 13:31:59 -0600 Subject: [PATCH 9/9] Revert "Avoid allocs on update" This reverts commit 486f01790c271cf09ac3dbffba0e40e526e81324. --- runtime/src/bank.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index ce2c70bb0bf691..2e628b405f5b9d 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -538,16 +538,12 @@ impl Bank { } pub fn update_recent_blockhashes(&self) { - let id = sysvar::recent_blockhashes::id(); - let mut account = self - .get_account(&id) - .or_else(|| Some(sysvar::recent_blockhashes::create_account(1))) - .unwrap(); let blockhash_queue = self.blockhash_queue.read().unwrap(); let recent_blockhash_iter = blockhash_queue.get_recent_blockhashes(); - sysvar::recent_blockhashes::update_account(&mut account, recent_blockhash_iter).unwrap(); - drop(blockhash_queue); - self.store_account(&id, &account); + self.store_account( + &sysvar::recent_blockhashes::id(), + &sysvar::recent_blockhashes::create_account_with_data(1, recent_blockhash_iter), + ); } // If the point values are not `normal`, bring them back into range and