SDK: Add sysvar to expose recent block hashes to programs (#6663)
* SDK: Add sysvar to expose recent block hashes to programs * Blockhashes is one word * Missed one * Avoid allocs on update * unwrap_or_else * Use iterators * Add microbench * Revert "unwrap_or_else" This reverts commit a8f8c3bfbe0d88e2229e67a29e9d2b489b93ff62. * Revert "Avoid allocs on update" This reverts commit 486f01790c271cf09ac3dbffba0e40e526e81324.
This commit is contained in:
parent
a0127e63c6
commit
5416c114cf
|
@ -9,6 +9,7 @@ use solana_runtime::loader_utils::create_invoke_instruction;
|
||||||
use solana_sdk::account::KeyedAccount;
|
use solana_sdk::account::KeyedAccount;
|
||||||
use solana_sdk::client::AsyncClient;
|
use solana_sdk::client::AsyncClient;
|
||||||
use solana_sdk::client::SyncClient;
|
use solana_sdk::client::SyncClient;
|
||||||
|
use solana_sdk::clock::MAX_RECENT_BLOCKHASHES;
|
||||||
use solana_sdk::genesis_block::create_genesis_block;
|
use solana_sdk::genesis_block::create_genesis_block;
|
||||||
use solana_sdk::instruction::InstructionError;
|
use solana_sdk::instruction::InstructionError;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
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) {
|
fn bench_bank_async_process_native_loader_transactions(bencher: &mut Bencher) {
|
||||||
do_bench_transactions(bencher, &async_bencher, &create_native_loader_transactions);
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -291,6 +291,7 @@ impl Bank {
|
||||||
bank.update_clock();
|
bank.update_clock();
|
||||||
bank.update_rent();
|
bank.update_rent();
|
||||||
bank.update_epoch_schedule();
|
bank.update_epoch_schedule();
|
||||||
|
bank.update_recent_blockhashes();
|
||||||
bank
|
bank
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +386,7 @@ impl Bank {
|
||||||
new.update_stake_history(Some(parent.epoch()));
|
new.update_stake_history(Some(parent.epoch()));
|
||||||
new.update_clock();
|
new.update_clock();
|
||||||
new.update_fees();
|
new.update_fees();
|
||||||
|
new.update_recent_blockhashes();
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,6 +541,15 @@ impl Bank {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_recent_blockhashes(&self) {
|
||||||
|
let blockhash_queue = self.blockhash_queue.read().unwrap();
|
||||||
|
let recent_blockhash_iter = blockhash_queue.get_recent_blockhashes();
|
||||||
|
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
|
// If the point values are not `normal`, bring them back into range and
|
||||||
// set them to the last value or 0.
|
// set them to the last value or 0.
|
||||||
fn check_point_values(
|
fn check_point_values(
|
||||||
|
@ -1599,6 +1610,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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -2023,18 +2046,6 @@ mod tests {
|
||||||
assert_eq!(bank.get_balance(&key.pubkey()), 1);
|
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]
|
#[test]
|
||||||
fn test_bank_tx_fee() {
|
fn test_bank_tx_fee() {
|
||||||
let arbitrary_transfer_amount = 42;
|
let arbitrary_transfer_amount = 42;
|
||||||
|
@ -3322,4 +3333,22 @@ mod tests {
|
||||||
// Non-native loader accounts can not be used for instruction processing
|
// Non-native loader accounts can not be used for instruction processing
|
||||||
bank.add_instruction_processor(mint_keypair.pubkey(), mock_ix_processor);
|
bank.add_instruction_processor(mint_keypair.pubkey(), mock_ix_processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_recent_blockhashes_sysvar() {
|
||||||
|
let (genesis_block, _mint_keypair) = create_genesis_block(500);
|
||||||
|
let mut bank = Arc::new(Bank::new(&genesis_block));
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,11 +114,16 @@ impl BlockhashQueue {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_recent_blockhashes(&self) -> impl Iterator<Item = (u64, &Hash)> {
|
||||||
|
(&self.ages).iter().map(|(k, v)| (v.hash_height, k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use bincode::serialize;
|
use bincode::serialize;
|
||||||
|
use solana_sdk::clock::MAX_RECENT_BLOCKHASHES;
|
||||||
use solana_sdk::hash::hash;
|
use solana_sdk::hash::hash;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -150,4 +155,21 @@ mod tests {
|
||||||
assert_eq!(last_hash, hash_queue.last_hash());
|
assert_eq!(last_hash, hash_queue.last_hash());
|
||||||
assert!(hash_queue.check_hash_age(&last_hash, 0));
|
assert!(hash_queue.check_hash_age(&last_hash, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_recent_blockhashes() {
|
||||||
|
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.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();
|
||||||
|
// Verify that the returned hashes are most recent
|
||||||
|
for (_slot, hash) in recent_blockhashes {
|
||||||
|
assert!(blockhash_queue.check_hash_age(hash, MAX_RECENT_BLOCKHASHES));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::pubkey::Pubkey;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod epoch_schedule;
|
pub mod epoch_schedule;
|
||||||
pub mod fees;
|
pub mod fees;
|
||||||
|
pub mod recent_blockhashes;
|
||||||
pub mod rent;
|
pub mod rent;
|
||||||
pub mod rewards;
|
pub mod rewards;
|
||||||
pub mod slot_hashes;
|
pub mod slot_hashes;
|
||||||
|
@ -14,6 +15,7 @@ pub fn is_sysvar_id(id: &Pubkey) -> bool {
|
||||||
clock::check_id(id)
|
clock::check_id(id)
|
||||||
|| epoch_schedule::check_id(id)
|
|| epoch_schedule::check_id(id)
|
||||||
|| fees::check_id(id)
|
|| fees::check_id(id)
|
||||||
|
|| recent_blockhashes::check_id(id)
|
||||||
|| rent::check_id(id)
|
|| rent::check_id(id)
|
||||||
|| rewards::check_id(id)
|
|| rewards::check_id(id)
|
||||||
|| slot_hashes::check_id(id)
|
|| slot_hashes::check_id(id)
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
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<Hash>);
|
||||||
|
|
||||||
|
impl Default for RecentBlockhashes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Vec::with_capacity(MAX_ENTRIES))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromIterator<&'a Hash> for RecentBlockhashes {
|
||||||
|
fn from_iter<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a Hash>,
|
||||||
|
{
|
||||||
|
let mut new = Self::default();
|
||||||
|
for i in iter {
|
||||||
|
new.0.push(*i)
|
||||||
|
}
|
||||||
|
new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecentBlockhashes {
|
||||||
|
pub fn from_account(account: &Account) -> Option<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<Hash>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_account(lamports: u64) -> Account {
|
||||||
|
Account::new(lamports, RecentBlockhashes::size_of(), &sysvar::id())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_account<'a, I>(account: &mut Account, recent_blockhash_iter: I) -> Option<()>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (u64, &'a Hash)>,
|
||||||
|
{
|
||||||
|
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<'a, I>(lamports: u64, recent_blockhash_iter: I) -> Account
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (u64, &'a Hash)>,
|
||||||
|
{
|
||||||
|
let mut account = create_account(lamports);
|
||||||
|
update_account(&mut account, recent_blockhash_iter).unwrap();
|
||||||
|
account
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::hash::Hash;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_account_empty() {
|
||||||
|
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 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]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue