sim: Override slot hashes account on simulation bank (#24543)

* sim: Override slot hashes during simulation

* Add simulation test program

* Address feedback

* Add AccountOverrides explicit type

* Cargo fmt
This commit is contained in:
Jon Cinque 2022-04-22 12:32:31 +02:00 committed by GitHub
parent 7d535e87d7
commit 0d51596224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2321 additions and 167 deletions

View File

@ -1157,6 +1157,7 @@ impl BankingStage {
transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
&mut execute_and_commit_timings.execute_timings,
None, // account_overrides
)
},
(),

2168
programs/bpf/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -84,6 +84,7 @@ members = [
"rust/sha",
"rust/sibling_inner_instruction",
"rust/sibling_instruction",
"rust/simulation",
"rust/spoof1",
"rust/spoof1_system",
"rust/sysvar",

View File

@ -94,6 +94,7 @@ fn main() {
"sha",
"sibling_inner_instruction",
"sibling_instruction",
"simulation",
"spoof1",
"spoof1_system",
"upgradeable",

View File

@ -0,0 +1,28 @@
[package]
name = "solana-bpf-rust-simulation"
version = "1.11.0"
description = "Solana BPF Program Simulation Differences"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-simulation"
edition = "2021"
[features]
test-bpf = []
[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=1.11.0" }
[dev-dependencies]
solana-logger = { path = "../../../../logger", version = "=1.11.0" }
solana-program-test = { path = "../../../../program-test", version = "=1.11.0" }
solana-sdk = { path = "../../../../sdk", version = "=1.11.0" }
solana-validator = { path = "../../../../validator", version = "=1.11.0" }
[lib]
crate-type = ["cdylib", "lib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,51 @@
use {
solana_program::{
account_info::{next_account_info, AccountInfo},
clock::Clock,
declare_id,
entrypoint::ProgramResult,
msg,
pubkey::Pubkey,
sysvar::Sysvar,
},
std::convert::TryInto,
};
declare_id!("Sim1jD5C35odT8mzctm8BWnjic8xW5xgeb5MbcbErTo");
solana_program::entrypoint!(process_instruction);
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let slot_history_account_info = next_account_info(account_info_iter)?;
let clock_account_info = next_account_info(account_info_iter)?;
// Slot is an u64 at the end of the structure
let data = slot_history_account_info.data.borrow();
let slot: u64 = u64::from_le_bytes(data[data.len() - 8..].try_into().unwrap());
let clock_from_cache = Clock::get().unwrap();
let clock_from_account = Clock::from_account_info(&clock_account_info).unwrap();
msg!("next_slot from slot history is {:?} ", slot);
msg!("clock from cache is in slot {:?} ", clock_from_cache.slot);
msg!(
"clock from account is in slot {:?} ",
clock_from_account.slot
);
if clock_from_cache.slot >= slot {
msg!("On-chain");
} else {
panic!("Simulation");
}
if clock_from_cache.slot != clock_from_account.slot {
panic!("Simulation");
}
Ok(())
}

View File

@ -0,0 +1,44 @@
#![cfg(feature = "test-bpf")]
use {
solana_bpf_rust_simulation::process_instruction,
solana_program_test::{processor, tokio, ProgramTest},
solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
signature::Signer,
sysvar,
transaction::Transaction,
},
};
#[tokio::test]
async fn no_panic() {
let program_id = Pubkey::new_unique();
let program_test = ProgramTest::new(
"solana_bpf_rust_simulation",
program_id,
processor!(process_instruction),
);
let mut context = program_test.start_with_context().await;
let transaction = Transaction::new_signed_with_payer(
&[Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(sysvar::slot_history::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
],
data: vec![],
}],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
context
.banks_client
.process_transaction_with_preflight(transaction)
.await
.unwrap();
}

View File

@ -0,0 +1,41 @@
#![cfg(feature = "test-bpf")]
use {
solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
sysvar,
},
solana_sdk::{signature::Signer, transaction::Transaction},
solana_validator::test_validator::*,
};
#[test]
fn no_panic() {
solana_logger::setup_with_default("solana_program_runtime=debug");
let program_id = Pubkey::new_unique();
let (test_validator, payer) = TestValidatorGenesis::default()
.add_program("solana_bpf_rust_simulation", program_id)
.start();
let rpc_client = test_validator.get_rpc_client();
let blockhash = rpc_client.get_latest_blockhash().unwrap();
let transaction = Transaction::new_signed_with_payer(
&[Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(sysvar::slot_history::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
],
data: vec![],
}],
Some(&payer.pubkey()),
&[&payer],
blockhash,
);
rpc_client
.send_and_confirm_transaction(&transaction)
.unwrap();
}

View File

@ -0,0 +1,25 @@
use solana_sdk::{account::AccountSharedData, pubkey::Pubkey, sysvar};
/// Encapsulates overridden accounts, typically used for transaction simulations
#[derive(Default)]
pub struct AccountOverrides {
pub slot_history: Option<AccountSharedData>,
}
impl AccountOverrides {
/// Sets in the slot history
///
/// Note: no checks are performed on the correctness of the contained data
pub fn set_slot_history(&mut self, slot_history: Option<AccountSharedData>) {
self.slot_history = slot_history;
}
/// Gets the account if it's found in the list of overrides
pub fn get(&self, pubkey: &Pubkey) -> Option<&AccountSharedData> {
if pubkey == &sysvar::slot_history::id() {
self.slot_history.as_ref()
} else {
None
}
}
}

View File

@ -1,5 +1,6 @@
use {
crate::{
account_overrides::AccountOverrides,
account_rent_state::{check_rent_state_with_account, RentState},
accounts_db::{
AccountShrinkThreshold, AccountsAddRootTiming, AccountsDb, AccountsDbConfig,
@ -240,6 +241,7 @@ impl Accounts {
error_counters: &mut ErrorCounters,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
account_overrides: Option<&AccountOverrides>,
) -> Result<LoadedTransaction> {
// Copy all the accounts
let message = tx.message();
@ -271,24 +273,29 @@ impl Accounts {
.is_active(&feature_set::instructions_sysvar_owned_by_sysvar::id()),
)
} else {
let (account, rent) = self
.accounts_db
.load_with_fixed_root(ancestors, key)
.map(|(mut account, _)| {
if message.is_writable(i) {
let rent_due = rent_collector
.collect_from_existing_account(
key,
&mut account,
self.accounts_db.filler_account_suffix.as_ref(),
)
.rent_amount;
(account, rent_due)
} else {
(account, 0)
}
})
.unwrap_or_default();
let (account, rent) = if let Some(account_override) =
account_overrides.and_then(|overrides| overrides.get(key))
{
(account_override.clone(), 0)
} else {
self.accounts_db
.load_with_fixed_root(ancestors, key)
.map(|(mut account, _)| {
if message.is_writable(i) {
let rent_due = rent_collector
.collect_from_existing_account(
key,
&mut account,
self.accounts_db.filler_account_suffix.as_ref(),
)
.rent_amount;
(account, rent_due)
} else {
(account, 0)
}
})
.unwrap_or_default()
};
if bpf_loader_upgradeable::check_id(account.owner()) {
if message.is_writable(i) && !message.is_upgradeable_loader_present() {
@ -488,6 +495,7 @@ impl Accounts {
Ok(account_indices)
}
#[allow(clippy::too_many_arguments)]
pub fn load_accounts(
&self,
ancestors: &Ancestors,
@ -498,6 +506,7 @@ impl Accounts {
rent_collector: &RentCollector,
feature_set: &FeatureSet,
fee_structure: &FeeStructure,
account_overrides: Option<&AccountOverrides>,
) -> Vec<TransactionLoadResult> {
txs.iter()
.zip(lock_results)
@ -527,6 +536,7 @@ impl Accounts {
error_counters,
rent_collector,
feature_set,
account_overrides,
) {
Ok(loaded_transaction) => loaded_transaction,
Err(e) => return (Err(e), None),
@ -1435,6 +1445,7 @@ mod tests {
rent_collector,
feature_set,
fee_structure,
None,
)
}
@ -3025,7 +3036,11 @@ mod tests {
accounts.accounts_db.clean_accounts(None, false, None);
}
fn load_accounts_no_store(accounts: &Accounts, tx: Transaction) -> Vec<TransactionLoadResult> {
fn load_accounts_no_store(
accounts: &Accounts,
tx: Transaction,
account_overrides: Option<&AccountOverrides>,
) -> Vec<TransactionLoadResult> {
let tx = SanitizedTransaction::from_transaction_for_tests(tx);
let rent_collector = RentCollector::default();
let mut hash_queue = BlockhashQueue::new(100);
@ -3042,6 +3057,7 @@ mod tests {
&rent_collector,
&FeatureSet::all_enabled(),
&FeeStructure::default(),
account_overrides,
)
}
@ -3067,11 +3083,47 @@ mod tests {
instructions,
);
let loaded_accounts = load_accounts_no_store(&accounts, tx);
let loaded_accounts = load_accounts_no_store(&accounts, tx, None);
assert_eq!(loaded_accounts.len(), 1);
assert!(loaded_accounts[0].0.is_err());
}
#[test]
fn test_overrides() {
solana_logger::setup();
let accounts = Accounts::new_with_config_for_tests(
Vec::new(),
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut account_overrides = AccountOverrides::default();
let slot_history_id = sysvar::slot_history::id();
let account = AccountSharedData::new(42, 0, &Pubkey::default());
account_overrides.set_slot_history(Some(account));
let keypair = Keypair::new();
let account = AccountSharedData::new(1_000_000, 0, &Pubkey::default());
accounts.store_slow_uncached(0, &keypair.pubkey(), &account);
let instructions = vec![CompiledInstruction::new(2, &(), vec![0])];
let tx = Transaction::new_with_compiled_instructions(
&[&keypair],
&[slot_history_id],
Hash::default(),
vec![native_loader::id()],
instructions,
);
let loaded_accounts = load_accounts_no_store(&accounts, tx, Some(&account_overrides));
assert_eq!(loaded_accounts.len(), 1);
let loaded_transaction = loaded_accounts[0].0.as_ref().unwrap();
assert_eq!(loaded_transaction.accounts[0].0, keypair.pubkey());
assert_eq!(loaded_transaction.accounts[1].0, slot_history_id);
assert_eq!(loaded_transaction.accounts[1].1.lamports(), 42);
}
fn create_accounts_prepare_if_nonce_account() -> (
Pubkey,
AccountSharedData,

View File

@ -37,6 +37,7 @@
use solana_sdk::recent_blockhashes_account;
use {
crate::{
account_overrides::AccountOverrides,
accounts::{
AccountAddressFilter, Accounts, LoadedTransaction, PubkeyAccountSlot,
TransactionLoadResult,
@ -117,7 +118,7 @@ use {
inflation::Inflation,
instruction::CompiledInstruction,
lamports::LamportsError,
message::SanitizedMessage,
message::{AccountKeys, SanitizedMessage},
native_loader,
native_token::sol_to_lamports,
nonce, nonce_account,
@ -127,7 +128,7 @@ use {
saturating_add_assign, secp256k1_program,
signature::{Keypair, Signature},
slot_hashes::SlotHashes,
slot_history::SlotHistory,
slot_history::{Check, SlotHistory},
stake::state::Delegation,
system_transaction,
sysvar::{self, Sysvar, SysvarId},
@ -3803,7 +3804,9 @@ impl Bank {
&self,
transaction: SanitizedTransaction,
) -> TransactionSimulationResult {
let number_of_accounts = transaction.message().account_keys().len();
let account_keys = transaction.message().account_keys();
let number_of_accounts = account_keys.len();
let account_overrides = self.get_account_overrides_for_simulation(&account_keys);
let batch = self.prepare_simulation_batch(transaction);
let mut timings = ExecuteTimings::default();
@ -3821,6 +3824,7 @@ impl Bank {
true,
true,
&mut timings,
Some(&account_overrides),
);
let post_simulation_accounts = loaded_transactions
@ -3867,6 +3871,27 @@ impl Bank {
}
}
fn get_account_overrides_for_simulation(&self, account_keys: &AccountKeys) -> AccountOverrides {
let mut account_overrides = AccountOverrides::default();
let slot_history_id = sysvar::slot_history::id();
if account_keys.iter().any(|pubkey| *pubkey == slot_history_id) {
let current_account = self.get_account_with_fixed_root(&slot_history_id);
let slot_history = current_account
.as_ref()
.map(|account| from_account::<SlotHistory, _>(account).unwrap())
.unwrap_or_default();
if slot_history.check(self.slot()) == Check::Found {
let ancestors = Ancestors::from(self.proper_ancestors().collect::<Vec<_>>());
if let Some((account, _)) =
self.load_slow_with_fixed_root(&ancestors, &slot_history_id)
{
account_overrides.set_slot_history(Some(account));
}
}
}
account_overrides
}
pub fn unlock_accounts(&self, batch: &mut TransactionBatch) {
if batch.needs_unlock {
batch.needs_unlock = false;
@ -4295,6 +4320,7 @@ impl Bank {
enable_log_recording: bool,
enable_return_data_recording: bool,
timings: &mut ExecuteTimings,
account_overrides: Option<&AccountOverrides>,
) -> LoadAndExecuteTransactionsOutput {
let sanitized_txs = batch.sanitized_transactions();
debug!("processing transactions: {}", sanitized_txs.len());
@ -4338,6 +4364,7 @@ impl Bank {
&self.rent_collector,
&self.feature_set,
&self.fee_structure,
account_overrides,
);
load_time.stop();
@ -5653,6 +5680,7 @@ impl Bank {
enable_log_recording,
enable_return_data_recording,
timings,
None,
);
let results = self.commit_transactions(
@ -17517,6 +17545,7 @@ pub(crate) mod tests {
&bank.rent_collector,
&bank.feature_set,
&FeeStructure::default(),
None,
);
let compute_budget = bank

View File

@ -5,6 +5,7 @@
extern crate lazy_static;
pub mod account_info;
pub mod account_overrides;
pub mod account_rent_state;
pub mod accounts;
pub mod accounts_background_service;