Skip to content

Commit

Permalink
Housekeeping (#62)
Browse files Browse the repository at this point in the history
* simplify a conditional

* remove old allow()

* replace some asserts with proper errors

* add more error logging

* use sysvar cache for clock in load_program

* check that the test in the README compiles

* combine some errors into LiteSVMError

* move errors to error.rs

* add test_nonexistent_program

* update changelog
  • Loading branch information
kevinheavey authored Apr 12, 2024
1 parent c2eda9e commit 7973d9b
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 131 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

- Add stake, config and vote programs ([#57](https://github.com/LiteSVM/litesvm/pull/57)).
- Implement blockhash and durable nonce checks ([#61](https://github.com/LiteSVM/litesvm/pull/61)).
- Add `error.rs` and new `LiteSVMError` type ([#62](https://github.com/LiteSVM/litesvm/pull/62)).
- Add more logging for users to make debugging errors easier ([#62](https://github.com/LiteSVM/litesvm/pull/62)).

### Changed

- Accept both legacy and versioned tx in simulate_transaction ([#58](https://github.com/LiteSVM/litesvm/pull/58)).
- Move `InvalidSysvarDataError` to `error.rs` ([#62](https://github.com/LiteSVM/litesvm/pull/62)).
- Change `set_account` to return `Result<(), LiteSVMError>` ([#62](https://github.com/LiteSVM/litesvm/pull/62)).

## [0.1.0] - 2024-04-02

Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ fn system_transfer() {

let from_account = svm.get_account(&from);
let to_account = svm.get_account(&to);

assert!(tx_res.is_ok());
assert_eq!(from_account.unwrap().lamports, 4936);
assert_eq!(to_account.unwrap().lamports, 64);
Expand Down
106 changes: 60 additions & 46 deletions src/accounts_db.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use log::error;
use solana_program::{
address_lookup_table::{self, error::AddressLookupError, state::AddressLookupTable},
bpf_loader, bpf_loader_deprecated,
Expand All @@ -10,7 +11,7 @@ use solana_program::{
AddressLoader, AddressLoaderError,
},
sysvar::{
self, clock::ID as CLOCK_ID, epoch_rewards::ID as EPOCH_REWARDS_ID,
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,
Expand All @@ -30,7 +31,7 @@ use solana_sdk::{
use solana_system_program::{get_system_account_kind, SystemAccountKind};
use std::{collections::HashMap, sync::Arc};

use crate::types::InvalidSysvarDataError;
use crate::error::{InvalidSysvarDataError, LiteSVMError};

const FEES_ID: Pubkey = solana_program::pubkey!("SysvarFees111111111111111111111111111111111");
const RECENT_BLOCKHASHES_ID: Pubkey =
Expand Down Expand Up @@ -75,9 +76,9 @@ impl AccountsDb {
&mut self,
pubkey: Pubkey,
account: AccountSharedData,
) -> Result<(), InvalidSysvarDataError> {
) -> Result<(), LiteSVMError> {
if account.executable() && pubkey != Pubkey::default() {
let loaded_program = self.load_program(&account).unwrap();
let loaded_program = self.load_program(&account)?;
self.programs_cache
.replenish(pubkey, Arc::new(loaded_program));
} else {
Expand Down Expand Up @@ -172,7 +173,7 @@ impl AccountsDb {
pub(crate) fn sync_accounts(
&mut self,
mut accounts: Vec<(Pubkey, AccountSharedData)>,
) -> Result<(), InvalidSysvarDataError> {
) -> Result<(), LiteSVMError> {
// need to add programdata accounts first if there are any
itertools::partition(&mut accounts, |x| {
x.1.owner() == &bpf_loader_upgradeable::id()
Expand All @@ -187,17 +188,12 @@ impl AccountsDb {
fn load_program(
&self,
program_account: &AccountSharedData,
// programdata_account: Option<&AccountSharedData>
) -> Result<LoadedProgram, InstructionError> {
let metrics = &mut LoadProgramMetrics::default();

let owner = program_account.owner();
let program_runtime_v1 = self.programs_cache.environments.program_runtime_v1.clone();
let clock_acc = self.get_account(&sysvar::clock::ID);
let clock: Clock = clock_acc
.map(|x| bincode::deserialize::<Clock>(x.data()).unwrap())
.unwrap_or_default();
let slot = clock.slot;
let slot = self.sysvar_cache.get_clock().unwrap().slot;

if bpf_loader::check_id(owner) | bpf_loader_deprecated::check_id(owner) {
LoadedProgram::new(
Expand All @@ -216,48 +212,63 @@ impl AccountsDb {
programdata_address,
}) = program_account.state()
else {
error!(
"Program account data does not deserialize to UpgradeableLoaderState::Program"
);
return Err(InstructionError::InvalidAccountData);
};
let programdata_account = self.get_account(&programdata_address).unwrap();
let programdata_account = self.get_account(&programdata_address).ok_or_else(|| {
error!("Program data account {programdata_address} not found");
InstructionError::MissingAccount
})?;
let program_data = programdata_account.data();
program_data
.get(UpgradeableLoaderState::size_of_programdata_metadata()..)
.ok_or(Box::new(InstructionError::InvalidAccountData).into())
.and_then(|programdata| {
LoadedProgram::new(
owner,
program_runtime_v1,
slot,
slot,
None,
programdata,
program_account
.data()
.len()
.saturating_add(program_data.len()),
metrics,
)
})
.map_err(|_| InstructionError::InvalidAccountData)
if let Some(programdata) =
program_data.get(UpgradeableLoaderState::size_of_programdata_metadata()..)
{
LoadedProgram::new(
owner,
program_runtime_v1,
slot,
slot,
None,
programdata,
program_account
.data()
.len()
.saturating_add(program_data.len()),
metrics).map_err(|_| {
error!("Error encountered when calling LoadedProgram::new() for bpf_loader_upgradeable.");
InstructionError::InvalidAccountData
})
} else {
error!("Index out of bounds using bpf_loader_upgradeable.");
Err(InstructionError::InvalidAccountData)
}
} else if loader_v4::check_id(owner) {
program_account
if let Some(elf_bytes) = program_account
.data()
.get(LoaderV4State::program_data_offset()..)
.ok_or(Box::new(InstructionError::InvalidAccountData).into())
.and_then(|elf_bytes| {
LoadedProgram::new(
&loader_v4::id(),
program_runtime_v1,
slot,
slot,
None,
elf_bytes,
program_account.data().len(),
metrics,
)
{
LoadedProgram::new(
&loader_v4::id(),
program_runtime_v1,
slot,
slot,
None,
elf_bytes,
program_account.data().len(),
metrics,
)
.map_err(|_| {
error!("Error encountered when calling LoadedProgram::new() for loader_v4.");
InstructionError::InvalidAccountData
})
.map_err(|_| InstructionError::InvalidAccountData)
} else {
error!("Index out of bounds using loader_v4.");
Err(InstructionError::InvalidAccountData)
}
} else {
error!("Owner does not match any expected loader.");
Err(InstructionError::IncorrectProgramId)
}
}
Expand Down Expand Up @@ -319,7 +330,10 @@ impl AccountsDb {

Ok(())
}
None => Err(TransactionError::AccountNotFound),
None => {
error!("Account {pubkey} not found when trying to withdraw fee.");
Err(TransactionError::AccountNotFound)
}
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use solana_sdk::instruction::InstructionError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum InvalidSysvarDataError {
#[error("Invalid Clock sysvar data.")]
Clock,
#[error("Invalid EpochRewards sysvar data.")]
EpochRewards,
#[error("Invalid EpochSchedule sysvar data.")]
EpochSchedule,
#[error("Invalid Fees sysvar data.")]
Fees,
#[error("Invalid LastRestartSlot sysvar data.")]
LastRestartSlot,
#[error("Invalid RecentBlockhashes sysvar data.")]
RecentBlockhashes,
#[error("Invalid Rent sysvar data.")]
Rent,
#[error("Invalid SlotHashes sysvar data.")]
SlotHashes,
#[error("Invalid StakeHistory sysvar data.")]
StakeHistory,
}

#[derive(Error, Debug)]
pub enum LiteSVMError {
#[error("{0}")]
InvalidSysvarData(#[from] InvalidSysvarDataError),
#[error("{0}")]
Instruction(#[from] InstructionError),
}
Loading

0 comments on commit 7973d9b

Please sign in to comment.