Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle sysvar updates #20

Merged
merged 4 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benches/banks_client_comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| {
svm.expire_blockhash();
let latest_blockhash = svm.latest_blockhash();
svm.set_account(counter_address, counter_acc(program_id));
let _ = svm.set_account(counter_address, counter_acc(program_id));
for deduper in 0..NUM_GREETINGS {
let tx = make_tx(
program_id,
Expand Down
139 changes: 130 additions & 9 deletions src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ use solana_program::{
clock::Clock,
instruction::InstructionError,
loader_v4::{self, LoaderV4State},
sysvar,
sysvar::{
self, clock::ID as CLOCK_ID, epoch_rewards::ID as EPOCH_REWARDS_ID,
epoch_schedule::ID as EPOCH_SCHEDULE_ID, last_restart_slot::ID as LAST_RESTART_SLOT_ID,
rent::ID as RENT_ID, slot_hashes::ID as SLOT_HASHES_ID,
stake_history::ID as STAKE_HISTORY_ID, Sysvar,
},
};
use solana_program_runtime::loaded_programs::{
LoadProgramMetrics, LoadedProgram, LoadedProgramsForTxBatch,
use solana_program_runtime::{
loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramsForTxBatch},
sysvar_cache::SysvarCache,
};
use solana_sdk::{
account::{AccountSharedData, ReadableAccount},
Expand All @@ -16,40 +22,155 @@ use solana_sdk::{
};
use std::{collections::HashMap, sync::Arc};

use crate::types::InvalidSysvarDataError;

const FEES_ID: Pubkey = solana_program::pubkey!("SysvarFees111111111111111111111111111111111");
const RECENT_BLOCKHASHES_ID: Pubkey =
solana_program::pubkey!("SysvarRecentB1ockHashes11111111111111111111");

fn handle_sysvar<F, T>(
cache: &mut SysvarCache,
method: F,
err_variant: InvalidSysvarDataError,
bytes: &[u8],
) -> Result<(), InvalidSysvarDataError>
where
T: Sysvar,
F: Fn(&mut SysvarCache, T),
{
method(
cache,
bincode::deserialize::<T>(bytes).map_err(|_| err_variant)?,
);
Ok(())
}

#[derive(Default)]
pub(crate) struct AccountsDb {
inner: HashMap<Pubkey, AccountSharedData>,
pub(crate) programs_cache: LoadedProgramsForTxBatch,
pub(crate) sysvar_cache: SysvarCache,
}

impl AccountsDb {
pub(crate) fn get_account(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
self.inner.get(pubkey).map(|acc| acc.to_owned())
}

pub(crate) fn add_account(&mut self, pubkey: Pubkey, data: AccountSharedData) {
if data.executable() && pubkey != Pubkey::default() {
let loaded_program = self.load_program(&data).unwrap();
/// We should only use this when we know we're not touching any executable or sysvar accounts,
/// or have already handled such cases.
pub(crate) fn add_account_no_checks(&mut self, pubkey: Pubkey, account: AccountSharedData) {
self.inner.insert(pubkey, account);
}

pub(crate) fn add_account(
&mut self,
pubkey: Pubkey,
account: AccountSharedData,
) -> Result<(), InvalidSysvarDataError> {
if account.executable() && pubkey != Pubkey::default() {
let loaded_program = self.load_program(&account).unwrap();
self.programs_cache
.replenish(pubkey, Arc::new(loaded_program));
} else {
self.maybe_handle_sysvar_account(pubkey, &account)?;
}
self.inner.insert(pubkey, data);
self.add_account_no_checks(pubkey, account);
Ok(())
}

fn maybe_handle_sysvar_account(
&mut self,
pubkey: Pubkey,
account: &AccountSharedData,
) -> Result<(), InvalidSysvarDataError> {
use InvalidSysvarDataError::{
Clock, EpochRewards, EpochSchedule, Fees, LastRestartSlot, RecentBlockhashes, Rent,
SlotHashes, StakeHistory,
};
let cache = &mut self.sysvar_cache;
#[allow(deprecated)]
match pubkey {
CLOCK_ID => {
handle_sysvar(cache, SysvarCache::set_clock, Clock, account.data())?;
}
EPOCH_REWARDS_ID => {
handle_sysvar(
cache,
SysvarCache::set_epoch_rewards,
EpochRewards,
account.data(),
)?;
}
EPOCH_SCHEDULE_ID => {
handle_sysvar(
cache,
SysvarCache::set_epoch_schedule,
EpochSchedule,
account.data(),
)?;
}
FEES_ID => {
handle_sysvar(cache, SysvarCache::set_fees, Fees, account.data())?;
}
LAST_RESTART_SLOT_ID => {
handle_sysvar(
cache,
SysvarCache::set_last_restart_slot,
LastRestartSlot,
account.data(),
)?;
}
RECENT_BLOCKHASHES_ID => {
handle_sysvar(
cache,
SysvarCache::set_recent_blockhashes,
RecentBlockhashes,
account.data(),
)?;
}
RENT_ID => {
handle_sysvar(cache, SysvarCache::set_rent, Rent, account.data())?;
}
SLOT_HASHES_ID => {
handle_sysvar(
cache,
SysvarCache::set_slot_hashes,
SlotHashes,
account.data(),
)?;
}
STAKE_HISTORY_ID => {
handle_sysvar(
cache,
SysvarCache::set_stake_history,
StakeHistory,
account.data(),
)?;
}
_ => {}
};
Ok(())
}

/// Skip the executable() checks for builtin accounts
pub(crate) fn add_builtin_account(&mut self, pubkey: Pubkey, data: AccountSharedData) {
self.inner.insert(pubkey, data);
}

pub(crate) fn sync_accounts(&mut self, mut accounts: Vec<(Pubkey, AccountSharedData)>) {
pub(crate) fn sync_accounts(
&mut self,
mut accounts: Vec<(Pubkey, AccountSharedData)>,
) -> Result<(), InvalidSysvarDataError> {
// need to add programdata accounts first if there are any
itertools::partition(&mut accounts, |x| {
x.1.owner() == &bpf_loader_upgradeable::id()
&& x.1.data().first().map_or(false, |byte| *byte == 3)
});
for (pubkey, acc) in accounts {
self.add_account(pubkey, acc);
self.add_account(pubkey, acc)?;
}
Ok(())
}

fn load_program(
Expand Down
72 changes: 41 additions & 31 deletions src/bank.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
use solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1;
use solana_loader_v4_program::create_program_runtime_environment_v2;
#[allow(deprecated)]
use solana_program::sysvar::{fees::Fees, recent_blockhashes::RecentBlockhashes};
use solana_program_runtime::{
compute_budget::ComputeBudget,
invoke_context::BuiltinFunctionWithContext,
loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramsForTxBatch},
log_collector::LogCollector,
message_processor::MessageProcessor,
sysvar_cache::SysvarCache,
timings::ExecuteTimings,
};
use solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
bpf_loader,
clock::Clock,
epoch_rewards::EpochRewards,
epoch_schedule::EpochSchedule,
feature_set::FeatureSet,
hash::Hash,
message::{
Expand All @@ -26,9 +29,11 @@ use solana_sdk::{
signature::{Keypair, Signature},
signer::Signer,
signers::Signers,
slot_history::Slot,
slot_hashes::SlotHashes,
slot_history::{Slot, SlotHistory},
stake_history::StakeHistory,
system_instruction, system_program,
sysvar::{Sysvar, SysvarId},
sysvar::{last_restart_slot::LastRestartSlot, Sysvar, SysvarId},
transaction::{MessageHash, SanitizedTransaction, TransactionError, VersionedTransaction},
transaction_context::{ExecutionRecord, IndexOfAccount, TransactionContext},
};
Expand All @@ -40,7 +45,10 @@ use crate::{
create_blockhash,
history::TransactionHistory,
spl::load_spl_programs,
types::{ExecutionResult, FailedTransactionMetadata, TransactionMetadata, TransactionResult},
types::{
ExecutionResult, FailedTransactionMetadata, InvalidSysvarDataError, TransactionMetadata,
TransactionResult,
},
utils::RentState,
};

Expand All @@ -60,7 +68,6 @@ pub struct LiteSVM {
accounts: AccountsDb,
//TODO compute budget
airdrop_kp: Keypair,
sysvar_cache: SysvarCache,
feature_set: Arc<FeatureSet>,
block_height: u64,
slot: Slot,
Expand All @@ -74,7 +81,6 @@ impl Default for LiteSVM {
Self {
accounts: Default::default(),
airdrop_kp: Keypair::new(),
sysvar_cache: Default::default(),
feature_set: Default::default(),
block_height: 0,
slot: 0,
Expand All @@ -96,7 +102,17 @@ impl LiteSVM {

pub fn with_sysvars(mut self) -> Self {
self.set_sysvar(&Clock::default());
self.set_sysvar(&EpochRewards::default());
self.set_sysvar(&EpochSchedule::default());
#[allow(deprecated)]
self.set_sysvar(&Fees::default());
self.set_sysvar(&LastRestartSlot::default());
#[allow(deprecated)]
self.set_sysvar(&RecentBlockhashes::default());
self.set_sysvar(&Rent::default());
self.set_sysvar(&SlotHashes::default());
self.set_sysvar(&SlotHistory::default());
self.set_sysvar(&StakeHistory::default());
self
}

Expand Down Expand Up @@ -137,7 +153,7 @@ impl LiteSVM {
}

pub fn with_lamports(mut self, lamports: u64) -> Self {
self.accounts.add_account(
self.accounts.add_account_no_checks(
self.airdrop_kp.pubkey(),
AccountSharedData::new(lamports, 0, &system_program::id()),
);
Expand All @@ -151,7 +167,8 @@ impl LiteSVM {

pub fn minimum_balance_for_rent_exemption(&self, data_len: usize) -> u64 {
1.max(
self.sysvar_cache
self.accounts
.sysvar_cache
.get_rent()
.unwrap_or_default()
.minimum_balance(data_len),
Expand All @@ -162,7 +179,11 @@ impl LiteSVM {
self.accounts.get_account(pubkey).map(Into::into)
}

pub fn set_account(&mut self, pubkey: Pubkey, data: Account) {
pub fn set_account(
&mut self,
pubkey: Pubkey,
data: Account,
) -> Result<(), InvalidSysvarDataError> {
self.accounts.add_account(pubkey, data.into())
}

Expand All @@ -182,24 +203,10 @@ impl LiteSVM {
where
T: Sysvar + SysvarId,
{
let Ok(data) = bincode::serialize(sysvar) else {
return;
};

let account = AccountSharedData::new_data(1, &sysvar, &solana_sdk::sysvar::id()).unwrap();

if T::id() == Clock::id() {
if let Ok(clock) = bincode::deserialize(&data) {
self.sysvar_cache.set_clock(clock);
self.accounts.add_account(Clock::id(), account);
}
} else if T::id() == Rent::id() {
if let Ok(rent) = bincode::deserialize(&data) {
self.sysvar_cache.set_rent(rent);
self.accounts.add_account(Rent::id(), account);
}
}
self.accounts.add_account(T::id(), account).unwrap();
}

pub fn get_transaction(&self, signature: &Signature) -> Option<&TransactionMetadata> {
self.history.get_transaction(signature)
}
Expand Down Expand Up @@ -229,7 +236,8 @@ impl LiteSVM {
.programs_cache
.replenish(program_id, Arc::new(builtin));
self.accounts
.add_account(program_id, AccountSharedData::new(0, 1, &bpf_loader::id()));
.add_account(program_id, AccountSharedData::new(0, 1, &bpf_loader::id()))
.unwrap();
}

pub fn store_program(&mut self, program_id: Pubkey, program_bytes: &[u8]) {
Expand All @@ -255,7 +263,7 @@ impl LiteSVM {
false,
)
.unwrap_or_default();
self.accounts.add_account(program_id, account);
self.accounts.add_account(program_id, account).unwrap();
self.accounts
.programs_cache
.replenish(program_id, Arc::new(loaded_program));
Expand Down Expand Up @@ -327,15 +335,15 @@ impl LiteSVM {
tx.message(),
&program_indices,
context,
*self.sysvar_cache.get_rent().unwrap_or_default(),
*self.accounts.sysvar_cache.get_rent().unwrap_or_default(),
Some(self.log_collector.clone()),
&self.accounts.programs_cache,
&mut programs_modified_by_tx,
&mut programs_updated_only_for_global_cache,
self.feature_set.clone(),
compute_budget,
&mut ExecuteTimings::default(),
&self.sysvar_cache,
&self.accounts.sysvar_cache,
*blockhash,
0,
u64::MAX,
Expand Down Expand Up @@ -364,7 +372,7 @@ impl LiteSVM {
let pubkey = context
.get_key_of_account_at_index(index as IndexOfAccount)
.map_err(|err| TransactionError::InstructionError(index as u8, err))?;
let rent = self.sysvar_cache.get_rent().unwrap_or_default();
let rent = self.accounts.sysvar_cache.get_rent().unwrap_or_default();

if !account.data().is_empty() {
let post_rent_state = RentState::from_account(&account, &rent);
Expand Down Expand Up @@ -460,7 +468,9 @@ impl LiteSVM {
} else {
self.history
.add_new_transaction(meta.signature, meta.clone());
self.accounts.sync_accounts(post_accounts);
self.accounts
.sync_accounts(post_accounts)
.expect("It shouldn't be possible to write invalid sysvars in send_transaction.");

TransactionResult::Ok(meta)
}
Expand Down
Loading