Merge pull request from GHSA-8v47-8c53-wwrc
* Track transaction check time separately from account loads * banking packet process metrics * Remove signature clone in status cache lookup * Reduce allocations when converting packets to transactions * Add blake3 hash of transaction messages in status cache * Bug fixes * fix tests and run fmt * Address feedback * fix simd tx entry verification * Fix rebase * Feedback * clean up * Add tests * Remove feature switch and fall back to signature check * Bump programs/bpf Cargo.lock * clippy * nudge benches * Bump `BankSlotDelta` frozen ABI hash` * Add blake3 to sdk/programs/Cargo.lock * nudge bpf tests * short circuit status cache checks Co-authored-by: Trent Nelson <trent@solana.com>
This commit is contained in:
@ -10,6 +10,7 @@ documentation = "https://docs.rs/solana-runtime"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
arrayref = "0.3.6"
|
||||
bincode = "1.3.1"
|
||||
blake3 = "0.3.6"
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
|
@ -86,7 +86,7 @@ pub fn create_native_loader_transactions(
|
||||
}
|
||||
|
||||
fn sync_bencher(bank: &Arc<Bank>, _bank_client: &BankClient, transactions: &[Transaction]) {
|
||||
let results = bank.process_transactions(&transactions);
|
||||
let results = bank.process_transactions(transactions);
|
||||
assert!(results.iter().all(Result::is_ok));
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use solana_sdk::{
|
||||
};
|
||||
use test::Bencher;
|
||||
|
||||
type BankStatusCache = StatusCache<Signature, ()>;
|
||||
type BankStatusCache = StatusCache<()>;
|
||||
|
||||
#[bench]
|
||||
fn test_statuscache_serialize(bencher: &mut Bencher) {
|
||||
|
@ -386,10 +386,10 @@ impl Accounts {
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
pub fn load_accounts(
|
||||
pub fn load_accounts<'a>(
|
||||
&self,
|
||||
ancestors: &Ancestors,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
lock_results: Vec<TransactionCheckResult>,
|
||||
hash_queue: &BlockhashQueue,
|
||||
error_counters: &mut ErrorCounters,
|
||||
@ -400,8 +400,7 @@ impl Accounts {
|
||||
secp256k1_program_enabled: feature_set
|
||||
.is_active(&feature_set::secp256k1_program_enabled::id()),
|
||||
};
|
||||
txs.iter()
|
||||
.zip(lock_results)
|
||||
txs.zip(lock_results)
|
||||
.map(|etx| match etx {
|
||||
(tx, (Ok(()), nonce_rollback)) => {
|
||||
let fee_calculator = nonce_rollback
|
||||
@ -793,14 +792,13 @@ impl Accounts {
|
||||
/// This function will prevent multiple threads from modifying the same account state at the
|
||||
/// same time
|
||||
#[must_use]
|
||||
pub fn lock_accounts(
|
||||
pub fn lock_accounts<'a>(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
demote_sysvar_write_locks: bool,
|
||||
) -> Vec<Result<()>> {
|
||||
use solana_sdk::sanitize::Sanitize;
|
||||
let keys: Vec<Result<_>> = txs
|
||||
.iter()
|
||||
.map(|tx| {
|
||||
tx.sanitize().map_err(TransactionError::from)?;
|
||||
|
||||
@ -825,15 +823,15 @@ impl Accounts {
|
||||
}
|
||||
|
||||
/// Once accounts are unlocked, new transactions that modify that state can enter the pipeline
|
||||
pub fn unlock_accounts(
|
||||
pub fn unlock_accounts<'a>(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
results: &[Result<()>],
|
||||
demote_sysvar_write_locks: bool,
|
||||
) {
|
||||
let mut account_locks = self.account_locks.lock().unwrap();
|
||||
debug!("bank unlock accounts");
|
||||
for (tx, lock_result) in txs.iter().zip(results) {
|
||||
for (tx, lock_result) in txs.zip(results) {
|
||||
self.unlock_account(
|
||||
tx,
|
||||
lock_result,
|
||||
@ -846,12 +844,12 @@ impl Accounts {
|
||||
/// Store the accounts into the DB
|
||||
// allow(clippy) needed for various gating flags
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn store_cached(
|
||||
pub fn store_cached<'a>(
|
||||
&self,
|
||||
slot: Slot,
|
||||
txs: &[Transaction],
|
||||
res: &[TransactionExecutionResult],
|
||||
loaded: &mut [TransactionLoadResult],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
res: &'a [TransactionExecutionResult],
|
||||
loaded: &'a mut [TransactionLoadResult],
|
||||
rent_collector: &RentCollector,
|
||||
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||
fix_recent_blockhashes_sysvar_delay: bool,
|
||||
@ -882,7 +880,7 @@ impl Accounts {
|
||||
|
||||
fn collect_accounts_to_store<'a>(
|
||||
&self,
|
||||
txs: &'a [Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
res: &'a [TransactionExecutionResult],
|
||||
loaded: &'a mut [TransactionLoadResult],
|
||||
rent_collector: &RentCollector,
|
||||
@ -1071,7 +1069,7 @@ mod tests {
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
accounts.load_accounts(
|
||||
&ancestors,
|
||||
&[tx],
|
||||
[tx].iter(),
|
||||
vec![(Ok(()), None)],
|
||||
&hash_queue,
|
||||
error_counters,
|
||||
@ -1673,7 +1671,7 @@ mod tests {
|
||||
);
|
||||
let tx = Transaction::new(&[&keypair0], message, Hash::default());
|
||||
let results0 = accounts.lock_accounts(
|
||||
&[tx.clone()],
|
||||
[tx.clone()].iter(),
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
|
||||
@ -1711,7 +1709,8 @@ mod tests {
|
||||
let tx1 = Transaction::new(&[&keypair1], message, Hash::default());
|
||||
let txs = vec![tx0, tx1];
|
||||
let results1 = accounts.lock_accounts(
|
||||
&txs, true, // demote_sysvar_write_locks
|
||||
txs.iter(),
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
|
||||
assert!(results1[0].is_ok()); // Read-only account (keypair1) can be referenced multiple times
|
||||
@ -1728,12 +1727,14 @@ mod tests {
|
||||
);
|
||||
|
||||
accounts.unlock_accounts(
|
||||
&[tx],
|
||||
[tx].iter(),
|
||||
&results0,
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
accounts.unlock_accounts(
|
||||
&txs, &results1, true, // demote_sysvar_write_locks
|
||||
txs.iter(),
|
||||
&results1,
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||
let message = Message::new_with_compiled_instructions(
|
||||
@ -1746,7 +1747,7 @@ mod tests {
|
||||
);
|
||||
let tx = Transaction::new(&[&keypair1], message, Hash::default());
|
||||
let results2 = accounts.lock_accounts(
|
||||
&[tx],
|
||||
[tx].iter(),
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
assert!(results2[0].is_ok()); // Now keypair1 account can be locked as writable
|
||||
@ -1813,7 +1814,8 @@ mod tests {
|
||||
loop {
|
||||
let txs = vec![writable_tx.clone()];
|
||||
let results = accounts_clone.clone().lock_accounts(
|
||||
&txs, true, // demote_sysvar_write_locks
|
||||
txs.iter(),
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
for result in results.iter() {
|
||||
if result.is_ok() {
|
||||
@ -1821,7 +1823,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
accounts_clone.unlock_accounts(
|
||||
&txs, &results, true, // demote_sysvar_write_locks
|
||||
txs.iter(),
|
||||
&results,
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
if exit_clone.clone().load(Ordering::Relaxed) {
|
||||
break;
|
||||
@ -1832,7 +1836,8 @@ mod tests {
|
||||
for _ in 0..5 {
|
||||
let txs = vec![readonly_tx.clone()];
|
||||
let results = accounts_arc.clone().lock_accounts(
|
||||
&txs, true, // demote_sysvar_write_locks
|
||||
txs.iter(),
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
if results[0].is_ok() {
|
||||
let counter_value = counter_clone.clone().load(Ordering::SeqCst);
|
||||
@ -1840,7 +1845,9 @@ mod tests {
|
||||
assert_eq!(counter_value, counter_clone.clone().load(Ordering::SeqCst));
|
||||
}
|
||||
accounts_arc.unlock_accounts(
|
||||
&txs, &results, true, // demote_sysvar_write_locks
|
||||
txs.iter(),
|
||||
&results,
|
||||
true, // demote_sysvar_write_locks
|
||||
);
|
||||
thread::sleep(time::Duration::from_millis(50));
|
||||
}
|
||||
@ -1922,7 +1929,7 @@ mod tests {
|
||||
.insert_new_readonly(&pubkey);
|
||||
}
|
||||
let collected_accounts = accounts.collect_accounts_to_store(
|
||||
&txs,
|
||||
txs.iter(),
|
||||
&loaders,
|
||||
loaded.as_mut_slice(),
|
||||
&rent_collector,
|
||||
@ -1991,7 +1998,7 @@ mod tests {
|
||||
let mut error_counters = ErrorCounters::default();
|
||||
accounts.load_accounts(
|
||||
&ancestors,
|
||||
&[tx],
|
||||
[tx].iter(),
|
||||
vec![(Ok(()), None)],
|
||||
&hash_queue,
|
||||
&mut error_counters,
|
||||
@ -2286,7 +2293,7 @@ mod tests {
|
||||
let accounts =
|
||||
Accounts::new_with_config(Vec::new(), &ClusterType::Development, HashSet::new(), false);
|
||||
let collected_accounts = accounts.collect_accounts_to_store(
|
||||
&txs,
|
||||
txs.iter(),
|
||||
&loaders,
|
||||
loaded.as_mut_slice(),
|
||||
&rent_collector,
|
||||
@ -2397,7 +2404,7 @@ mod tests {
|
||||
let accounts =
|
||||
Accounts::new_with_config(Vec::new(), &ClusterType::Development, HashSet::new(), false);
|
||||
let collected_accounts = accounts.collect_accounts_to_store(
|
||||
&txs,
|
||||
txs.iter(),
|
||||
&loaders,
|
||||
loaded.as_mut_slice(),
|
||||
&rent_collector,
|
||||
|
@ -118,7 +118,7 @@ pub struct ErrorCounters {
|
||||
pub blockhash_not_found: usize,
|
||||
pub blockhash_too_old: usize,
|
||||
pub call_chain_too_deep: usize,
|
||||
pub duplicate_signature: usize,
|
||||
pub already_processed: usize,
|
||||
pub instruction_error: usize,
|
||||
pub insufficient_funds: usize,
|
||||
pub invalid_account_for_fee: usize,
|
||||
|
@ -12,6 +12,7 @@ use crate::{
|
||||
blockhash_queue::BlockhashQueue,
|
||||
builtins::{self, ActivationType},
|
||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||
hashed_transaction::{HashedTransaction, HashedTransactionSlice},
|
||||
inline_spl_token_v2_0,
|
||||
instruction_recorder::InstructionRecorder,
|
||||
log_collector::LogCollector,
|
||||
@ -76,6 +77,7 @@ use solana_stake_program::stake_state::{
|
||||
};
|
||||
use solana_vote_program::vote_instruction::VoteInstruction;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
convert::{TryFrom, TryInto},
|
||||
@ -98,6 +100,7 @@ pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ExecuteTimings {
|
||||
pub check_us: u64,
|
||||
pub load_us: u64,
|
||||
pub execute_us: u64,
|
||||
pub store_us: u64,
|
||||
@ -106,6 +109,7 @@ pub struct ExecuteTimings {
|
||||
|
||||
impl ExecuteTimings {
|
||||
pub fn accumulate(&mut self, other: &ExecuteTimings) {
|
||||
self.check_us += other.check_us;
|
||||
self.load_us += other.load_us;
|
||||
self.execute_us += other.execute_us;
|
||||
self.store_us += other.store_us;
|
||||
@ -113,8 +117,8 @@ impl ExecuteTimings {
|
||||
}
|
||||
}
|
||||
|
||||
type BankStatusCache = StatusCache<Signature, Result<()>>;
|
||||
#[frozen_abi(digest = "4mSWwHd4RrLjCXH7RFrm6K3wZSsi9DfVJK3Ngz9jKk7D")]
|
||||
type BankStatusCache = StatusCache<Result<()>>;
|
||||
#[frozen_abi(digest = "3TYCJ7hSJ5ig2NmnwxSn1ggkzm6JCmMHoyRMBQQsLCa3")]
|
||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||
type TransactionAccountRefCells = Vec<Rc<RefCell<AccountSharedData>>>;
|
||||
type TransactionAccountDepRefCells = Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>;
|
||||
@ -2350,10 +2354,15 @@ impl Bank {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_transaction_statuses(&self, txs: &[Transaction], res: &[TransactionExecutionResult]) {
|
||||
fn update_transaction_statuses(
|
||||
&self,
|
||||
hashed_txs: &[HashedTransaction],
|
||||
res: &[TransactionExecutionResult],
|
||||
) {
|
||||
let mut status_cache = self.src.status_cache.write().unwrap();
|
||||
assert_eq!(txs.len(), res.len());
|
||||
for (tx, (res, _nonce_rollback)) in txs.iter().zip(res) {
|
||||
assert_eq!(hashed_txs.len(), res.len());
|
||||
for (hashed_tx, (res, _nonce_rollback)) in hashed_txs.iter().zip(res) {
|
||||
let tx = hashed_tx.transaction();
|
||||
if Self::can_commit(res) && !tx.signatures.is_empty() {
|
||||
status_cache.insert(
|
||||
&tx.message().recent_blockhash,
|
||||
@ -2361,6 +2370,12 @@ impl Bank {
|
||||
self.slot(),
|
||||
res.clone(),
|
||||
);
|
||||
status_cache.insert(
|
||||
&tx.message().recent_blockhash,
|
||||
&hashed_tx.message_hash,
|
||||
self.slot(),
|
||||
res.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2399,27 +2414,32 @@ impl Bank {
|
||||
tick_height % self.ticks_per_slot == 0
|
||||
}
|
||||
|
||||
/// Process a Transaction. This is used for unit tests and simply calls the vector
|
||||
/// Bank::process_transactions method
|
||||
pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
|
||||
let txs = vec![tx.clone()];
|
||||
self.process_transactions(&txs)[0].clone()?;
|
||||
tx.signatures
|
||||
.get(0)
|
||||
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
|
||||
}
|
||||
|
||||
pub fn demote_sysvar_write_locks(&self) -> bool {
|
||||
self.feature_set
|
||||
.is_active(&feature_set::demote_sysvar_write_locks::id())
|
||||
}
|
||||
|
||||
pub fn prepare_batch<'a, 'b>(&'a self, txs: &'b [Transaction]) -> TransactionBatch<'a, 'b> {
|
||||
let lock_results = self
|
||||
.rc
|
||||
.accounts
|
||||
.lock_accounts(txs, self.demote_sysvar_write_locks());
|
||||
TransactionBatch::new(lock_results, &self, txs)
|
||||
pub fn prepare_batch<'a, 'b>(
|
||||
&'a self,
|
||||
txs: impl Iterator<Item = &'b Transaction>,
|
||||
) -> TransactionBatch<'a, 'b> {
|
||||
let hashed_txs: Vec<HashedTransaction> = txs.map(HashedTransaction::from).collect();
|
||||
let lock_results = self.rc.accounts.lock_accounts(
|
||||
hashed_txs.as_transactions_iter(),
|
||||
self.demote_sysvar_write_locks(),
|
||||
);
|
||||
TransactionBatch::new(lock_results, &self, Cow::Owned(hashed_txs))
|
||||
}
|
||||
|
||||
pub fn prepare_hashed_batch<'a, 'b>(
|
||||
&'a self,
|
||||
hashed_txs: &'b [HashedTransaction],
|
||||
) -> TransactionBatch<'a, 'b> {
|
||||
let lock_results = self.rc.accounts.lock_accounts(
|
||||
hashed_txs.as_transactions_iter(),
|
||||
self.demote_sysvar_write_locks(),
|
||||
);
|
||||
TransactionBatch::new(lock_results, &self, Cow::Borrowed(hashed_txs))
|
||||
}
|
||||
|
||||
pub fn prepare_simulation_batch<'a, 'b>(
|
||||
@ -2430,7 +2450,8 @@ impl Bank {
|
||||
.iter()
|
||||
.map(|tx| tx.sanitize().map_err(|e| e.into()))
|
||||
.collect();
|
||||
let mut batch = TransactionBatch::new(lock_results, &self, txs);
|
||||
let hashed_txs = txs.iter().map(HashedTransaction::from).collect();
|
||||
let mut batch = TransactionBatch::new(lock_results, &self, hashed_txs);
|
||||
batch.needs_unlock = false;
|
||||
batch
|
||||
}
|
||||
@ -2480,7 +2501,7 @@ impl Bank {
|
||||
if batch.needs_unlock {
|
||||
batch.needs_unlock = false;
|
||||
self.rc.accounts.unlock_accounts(
|
||||
batch.transactions(),
|
||||
batch.transactions_iter(),
|
||||
batch.lock_results(),
|
||||
self.demote_sysvar_write_locks(),
|
||||
)
|
||||
@ -2495,16 +2516,15 @@ impl Bank {
|
||||
self.rc.accounts.accounts_db.set_shrink_paths(paths);
|
||||
}
|
||||
|
||||
fn check_age(
|
||||
fn check_age<'a>(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
lock_results: Vec<Result<()>>,
|
||||
max_age: usize,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Vec<TransactionCheckResult> {
|
||||
let hash_queue = self.blockhash_queue.read().unwrap();
|
||||
txs.iter()
|
||||
.zip(lock_results)
|
||||
txs.zip(lock_results)
|
||||
.map(|(tx, lock_res)| match lock_res {
|
||||
Ok(()) => {
|
||||
let message = tx.message();
|
||||
@ -2526,47 +2546,62 @@ impl Bank {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn check_signatures(
|
||||
fn is_tx_already_processed(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
hashed_tx: &HashedTransaction,
|
||||
status_cache: &StatusCache<Result<()>>,
|
||||
) -> bool {
|
||||
let tx = hashed_tx.transaction();
|
||||
if status_cache
|
||||
.get_status(
|
||||
&hashed_tx.message_hash,
|
||||
&tx.message().recent_blockhash,
|
||||
&self.ancestors,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to signature check in case this validator has only recently started
|
||||
// adding the message hash to the status cache.
|
||||
return tx
|
||||
.signatures
|
||||
.get(0)
|
||||
.and_then(|sig0| {
|
||||
status_cache.get_status(sig0, &tx.message().recent_blockhash, &self.ancestors)
|
||||
})
|
||||
.is_some();
|
||||
}
|
||||
|
||||
fn check_status_cache(
|
||||
&self,
|
||||
hashed_txs: &[HashedTransaction],
|
||||
lock_results: Vec<TransactionCheckResult>,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Vec<TransactionCheckResult> {
|
||||
let rcache = self.src.status_cache.read().unwrap();
|
||||
txs.iter()
|
||||
hashed_txs
|
||||
.iter()
|
||||
.zip(lock_results)
|
||||
.map(|(tx, lock_res)| {
|
||||
if tx.signatures.is_empty() {
|
||||
return lock_res;
|
||||
.map(|(hashed_tx, (lock_res, nonce_rollback))| {
|
||||
if lock_res.is_ok() && self.is_tx_already_processed(hashed_tx, &rcache) {
|
||||
error_counters.already_processed += 1;
|
||||
return (Err(TransactionError::AlreadyProcessed), None);
|
||||
}
|
||||
{
|
||||
let (lock_res, _nonce_rollback) = &lock_res;
|
||||
if lock_res.is_ok()
|
||||
&& rcache
|
||||
.get_status(
|
||||
&tx.signatures[0],
|
||||
&tx.message().recent_blockhash,
|
||||
&self.ancestors,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
error_counters.duplicate_signature += 1;
|
||||
return (Err(TransactionError::DuplicateSignature), None);
|
||||
}
|
||||
}
|
||||
lock_res
|
||||
|
||||
(lock_res, nonce_rollback)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn filter_by_vote_transactions(
|
||||
fn filter_by_vote_transactions<'a>(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
lock_results: Vec<TransactionCheckResult>,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Vec<TransactionCheckResult> {
|
||||
txs.iter()
|
||||
.zip(lock_results)
|
||||
txs.zip(lock_results)
|
||||
.map(|(tx, lock_res)| {
|
||||
if lock_res.0.is_ok() {
|
||||
if is_simple_vote_transaction(tx) {
|
||||
@ -2615,24 +2650,33 @@ impl Bank {
|
||||
|
||||
pub fn check_transactions(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
hashed_txs: &[HashedTransaction],
|
||||
lock_results: &[Result<()>],
|
||||
max_age: usize,
|
||||
mut error_counters: &mut ErrorCounters,
|
||||
) -> Vec<TransactionCheckResult> {
|
||||
let age_results = self.check_age(txs, lock_results.to_vec(), max_age, &mut error_counters);
|
||||
let sigcheck_results = self.check_signatures(txs, age_results, &mut error_counters);
|
||||
let age_results = self.check_age(
|
||||
hashed_txs.as_transactions_iter(),
|
||||
lock_results.to_vec(),
|
||||
max_age,
|
||||
&mut error_counters,
|
||||
);
|
||||
let cache_results = self.check_status_cache(hashed_txs, age_results, &mut error_counters);
|
||||
if self.upgrade_epoch() {
|
||||
// Reject all non-vote transactions
|
||||
self.filter_by_vote_transactions(txs, sigcheck_results, &mut error_counters)
|
||||
self.filter_by_vote_transactions(
|
||||
hashed_txs.as_transactions_iter(),
|
||||
cache_results,
|
||||
&mut error_counters,
|
||||
)
|
||||
} else {
|
||||
sigcheck_results
|
||||
cache_results
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_balances(&self, batch: &TransactionBatch) -> TransactionBalances {
|
||||
let mut balances: TransactionBalances = vec![];
|
||||
for transaction in batch.transactions() {
|
||||
for transaction in batch.transactions_iter() {
|
||||
let mut transaction_balances: Vec<u64> = vec![];
|
||||
for account_key in transaction.message.account_keys.iter() {
|
||||
transaction_balances.push(self.get_balance(account_key));
|
||||
@ -2704,10 +2748,10 @@ impl Bank {
|
||||
error_counters.instruction_error
|
||||
);
|
||||
}
|
||||
if 0 != error_counters.duplicate_signature {
|
||||
if 0 != error_counters.already_processed {
|
||||
inc_new_counter_info!(
|
||||
"bank-process_transactions-error-duplicate_signature",
|
||||
error_counters.duplicate_signature
|
||||
"bank-process_transactions-error-already_processed",
|
||||
error_counters.already_processed
|
||||
);
|
||||
}
|
||||
if 0 != error_counters.not_allowed_during_cluster_maintenance {
|
||||
@ -2851,11 +2895,10 @@ impl Bank {
|
||||
u64,
|
||||
u64,
|
||||
) {
|
||||
let txs = batch.transactions();
|
||||
debug!("processing transactions: {}", txs.len());
|
||||
inc_new_counter_info!("bank-process_transactions", txs.len());
|
||||
let hashed_txs = batch.hashed_transactions();
|
||||
debug!("processing transactions: {}", hashed_txs.len());
|
||||
inc_new_counter_info!("bank-process_transactions", hashed_txs.len());
|
||||
let mut error_counters = ErrorCounters::default();
|
||||
let mut load_time = Measure::start("accounts_load");
|
||||
|
||||
let retryable_txs: Vec<_> = batch
|
||||
.lock_results()
|
||||
@ -2871,12 +2914,20 @@ impl Bank {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let sig_results =
|
||||
self.check_transactions(txs, batch.lock_results(), max_age, &mut error_counters);
|
||||
let mut check_time = Measure::start("check_transactions");
|
||||
let check_results = self.check_transactions(
|
||||
hashed_txs,
|
||||
batch.lock_results(),
|
||||
max_age,
|
||||
&mut error_counters,
|
||||
);
|
||||
check_time.stop();
|
||||
|
||||
let mut load_time = Measure::start("accounts_load");
|
||||
let mut loaded_accounts = self.rc.accounts.load_accounts(
|
||||
&self.ancestors,
|
||||
txs,
|
||||
sig_results,
|
||||
hashed_txs.as_transactions_iter(),
|
||||
check_results,
|
||||
&self.blockhash_queue.read().unwrap(),
|
||||
&mut error_counters,
|
||||
&self.rent_collector,
|
||||
@ -2887,20 +2938,19 @@ impl Bank {
|
||||
let mut execution_time = Measure::start("execution_time");
|
||||
let mut signature_count: u64 = 0;
|
||||
let mut inner_instructions: Vec<Option<InnerInstructionsList>> =
|
||||
Vec::with_capacity(txs.len());
|
||||
let mut transaction_log_messages = Vec::with_capacity(txs.len());
|
||||
Vec::with_capacity(hashed_txs.len());
|
||||
let mut transaction_log_messages = Vec::with_capacity(hashed_txs.len());
|
||||
let bpf_compute_budget = self
|
||||
.bpf_compute_budget
|
||||
.unwrap_or_else(BpfComputeBudget::new);
|
||||
|
||||
let executed: Vec<TransactionExecutionResult> = loaded_accounts
|
||||
.iter_mut()
|
||||
.zip(txs)
|
||||
.zip(hashed_txs.as_transactions_iter())
|
||||
.map(|(accs, tx)| match accs {
|
||||
(Err(e), _nonce_rollback) => (Err(e.clone()), None),
|
||||
(Ok(loaded_transaction), nonce_rollback) => {
|
||||
signature_count += u64::from(tx.message().header.num_required_signatures);
|
||||
|
||||
let executors = self.get_executors(&tx.message, &loaded_transaction.loaders);
|
||||
|
||||
let (account_refcells, account_dep_refcells, loader_refcells) =
|
||||
@ -2984,11 +3034,13 @@ impl Bank {
|
||||
execution_time.stop();
|
||||
|
||||
debug!(
|
||||
"load: {}us execute: {}us txs_len={}",
|
||||
"check: {}us load: {}us execute: {}us txs_len={}",
|
||||
check_time.as_us(),
|
||||
load_time.as_us(),
|
||||
execution_time.as_us(),
|
||||
txs.len(),
|
||||
hashed_txs.len(),
|
||||
);
|
||||
timings.check_us += check_time.as_us();
|
||||
timings.load_us += load_time.as_us();
|
||||
timings.execute_us += execution_time.as_us();
|
||||
|
||||
@ -2997,7 +3049,8 @@ impl Bank {
|
||||
let transaction_log_collector_config =
|
||||
self.transaction_log_collector_config.read().unwrap();
|
||||
|
||||
for (i, ((r, _nonce_rollback), tx)) in executed.iter().zip(txs).enumerate() {
|
||||
for (i, ((r, _nonce_rollback), hashed_tx)) in executed.iter().zip(hashed_txs).enumerate() {
|
||||
let tx = hashed_tx.transaction();
|
||||
if let Some(debug_keys) = &self.transaction_debug_keys {
|
||||
for key in &tx.message.account_keys {
|
||||
if debug_keys.contains(key) {
|
||||
@ -3080,9 +3133,9 @@ impl Bank {
|
||||
)
|
||||
}
|
||||
|
||||
fn filter_program_errors_and_collect_fee(
|
||||
fn filter_program_errors_and_collect_fee<'a>(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
executed: &[TransactionExecutionResult],
|
||||
) -> Vec<Result<()>> {
|
||||
let hash_queue = self.blockhash_queue.read().unwrap();
|
||||
@ -3093,7 +3146,6 @@ impl Bank {
|
||||
};
|
||||
|
||||
let results = txs
|
||||
.iter()
|
||||
.zip(executed)
|
||||
.map(|(tx, (res, nonce_rollback))| {
|
||||
let (fee_calculator, is_durable_nonce) = nonce_rollback
|
||||
@ -3142,7 +3194,7 @@ impl Bank {
|
||||
|
||||
pub fn commit_transactions(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
hashed_txs: &[HashedTransaction],
|
||||
loaded_accounts: &mut [TransactionLoadResult],
|
||||
executed: &[TransactionExecutionResult],
|
||||
tx_count: u64,
|
||||
@ -3160,8 +3212,8 @@ impl Bank {
|
||||
inc_new_counter_info!("bank-process_transactions-txs", tx_count as usize);
|
||||
inc_new_counter_info!("bank-process_transactions-sigs", signature_count as usize);
|
||||
|
||||
if !txs.is_empty() {
|
||||
let processed_tx_count = txs.len() as u64;
|
||||
if !hashed_txs.is_empty() {
|
||||
let processed_tx_count = hashed_txs.len() as u64;
|
||||
let failed_tx_count = processed_tx_count.saturating_sub(tx_count);
|
||||
self.transaction_error_count
|
||||
.fetch_add(failed_tx_count, Relaxed);
|
||||
@ -3180,7 +3232,7 @@ impl Bank {
|
||||
let mut write_time = Measure::start("write_time");
|
||||
self.rc.accounts.store_cached(
|
||||
self.slot(),
|
||||
txs,
|
||||
hashed_txs.as_transactions_iter(),
|
||||
executed,
|
||||
loaded_accounts,
|
||||
&self.rent_collector,
|
||||
@ -3190,14 +3242,23 @@ impl Bank {
|
||||
);
|
||||
self.collect_rent(executed, loaded_accounts);
|
||||
|
||||
let overwritten_vote_accounts = self.update_cached_accounts(txs, executed, loaded_accounts);
|
||||
let overwritten_vote_accounts = self.update_cached_accounts(
|
||||
hashed_txs.as_transactions_iter(),
|
||||
executed,
|
||||
loaded_accounts,
|
||||
);
|
||||
|
||||
// once committed there is no way to unroll
|
||||
write_time.stop();
|
||||
debug!("store: {}us txs_len={}", write_time.as_us(), txs.len(),);
|
||||
debug!(
|
||||
"store: {}us txs_len={}",
|
||||
write_time.as_us(),
|
||||
hashed_txs.len()
|
||||
);
|
||||
timings.store_us += write_time.as_us();
|
||||
self.update_transaction_statuses(txs, &executed);
|
||||
let fee_collection_results = self.filter_program_errors_and_collect_fee(txs, executed);
|
||||
self.update_transaction_statuses(hashed_txs, &executed);
|
||||
let fee_collection_results =
|
||||
self.filter_program_errors_and_collect_fee(hashed_txs.as_transactions_iter(), executed);
|
||||
|
||||
TransactionResults {
|
||||
fee_collection_results,
|
||||
@ -3796,7 +3857,7 @@ impl Bank {
|
||||
);
|
||||
|
||||
let results = self.commit_transactions(
|
||||
batch.transactions(),
|
||||
batch.hashed_transactions(),
|
||||
&mut loaded_accounts,
|
||||
&executed,
|
||||
tx_count,
|
||||
@ -3816,11 +3877,26 @@ impl Bank {
|
||||
)
|
||||
}
|
||||
|
||||
/// Process a Transaction. This is used for unit tests and simply calls the vector
|
||||
/// Bank::process_transactions method
|
||||
pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
|
||||
let batch = self.prepare_batch(std::iter::once(tx));
|
||||
self.process_transaction_batch(&batch)[0].clone()?;
|
||||
tx.signatures
|
||||
.get(0)
|
||||
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
|
||||
let batch = self.prepare_batch(txs);
|
||||
let batch = self.prepare_batch(txs.iter());
|
||||
self.process_transaction_batch(&batch)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn process_transaction_batch(&self, batch: &TransactionBatch) -> Vec<Result<()>> {
|
||||
self.load_execute_and_commit_transactions(
|
||||
&batch,
|
||||
batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
false,
|
||||
false,
|
||||
@ -4384,9 +4460,9 @@ impl Bank {
|
||||
}
|
||||
|
||||
/// a bank-level cache of vote accounts
|
||||
fn update_cached_accounts(
|
||||
fn update_cached_accounts<'a>(
|
||||
&self,
|
||||
txs: &[Transaction],
|
||||
txs: impl Iterator<Item = &'a Transaction>,
|
||||
res: &[TransactionExecutionResult],
|
||||
loaded: &[TransactionLoadResult],
|
||||
) -> Vec<OverwrittenVoteAccount> {
|
||||
@ -7562,7 +7638,7 @@ pub(crate) mod tests {
|
||||
];
|
||||
let initial_balance = bank.get_balance(&leader);
|
||||
|
||||
let results = bank.filter_program_errors_and_collect_fee(&[tx1, tx2], &results);
|
||||
let results = bank.filter_program_errors_and_collect_fee([tx1, tx2].iter(), &results);
|
||||
bank.freeze();
|
||||
assert_eq!(
|
||||
bank.get_balance(&leader),
|
||||
@ -7688,7 +7764,7 @@ pub(crate) mod tests {
|
||||
system_transaction::transfer(&mint_keypair, &alice.pubkey(), 1, genesis_config.hash());
|
||||
let pay_alice = vec![tx1];
|
||||
|
||||
let lock_result = bank.prepare_batch(&pay_alice);
|
||||
let lock_result = bank.prepare_batch(pay_alice.iter());
|
||||
let results_alice = bank
|
||||
.load_execute_and_commit_transactions(
|
||||
&lock_result,
|
||||
@ -7741,7 +7817,7 @@ pub(crate) mod tests {
|
||||
let tx = Transaction::new(&[&key0], message, genesis_config.hash());
|
||||
let txs = vec![tx];
|
||||
|
||||
let batch0 = bank.prepare_batch(&txs);
|
||||
let batch0 = bank.prepare_batch(txs.iter());
|
||||
assert!(batch0.lock_results()[0].is_ok());
|
||||
|
||||
// Try locking accounts, locking a previously read-only account as writable
|
||||
@ -7759,7 +7835,7 @@ pub(crate) mod tests {
|
||||
let tx = Transaction::new(&[&key1], message, genesis_config.hash());
|
||||
let txs = vec![tx];
|
||||
|
||||
let batch1 = bank.prepare_batch(&txs);
|
||||
let batch1 = bank.prepare_batch(txs.iter());
|
||||
assert!(batch1.lock_results()[0].is_err());
|
||||
|
||||
// Try locking a previously read-only account a 2nd time; should succeed
|
||||
@ -7776,7 +7852,7 @@ pub(crate) mod tests {
|
||||
let tx = Transaction::new(&[&key2], message, genesis_config.hash());
|
||||
let txs = vec![tx];
|
||||
|
||||
let batch2 = bank.prepare_batch(&txs);
|
||||
let batch2 = bank.prepare_batch(txs.iter());
|
||||
assert!(batch2.lock_results()[0].is_ok());
|
||||
}
|
||||
|
||||
@ -7839,9 +7915,38 @@ pub(crate) mod tests {
|
||||
assert!(Arc::ptr_eq(&bank.parents()[0], &parent));
|
||||
}
|
||||
|
||||
/// Verifies that transactions are dropped if they have already been processed
|
||||
#[test]
|
||||
fn test_tx_already_processed() {
|
||||
let (genesis_config, mint_keypair) = create_genesis_config(2);
|
||||
let bank = Bank::new(&genesis_config);
|
||||
|
||||
let key1 = Keypair::new();
|
||||
let mut tx =
|
||||
system_transaction::transfer(&mint_keypair, &key1.pubkey(), 1, genesis_config.hash());
|
||||
|
||||
// First process `tx` so that the status cache is updated
|
||||
assert_eq!(bank.process_transaction(&tx), Ok(()));
|
||||
|
||||
// Ensure that signature check works
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx),
|
||||
Err(TransactionError::AlreadyProcessed)
|
||||
);
|
||||
|
||||
// Clear transaction signature
|
||||
tx.signatures[0] = Signature::default();
|
||||
|
||||
// Ensure that message hash check works
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx),
|
||||
Err(TransactionError::AlreadyProcessed)
|
||||
);
|
||||
}
|
||||
|
||||
/// Verifies that last ids and status cache are correctly referenced from parent
|
||||
#[test]
|
||||
fn test_bank_parent_duplicate_signature() {
|
||||
fn test_bank_parent_already_processed() {
|
||||
let (genesis_config, mint_keypair) = create_genesis_config(2);
|
||||
let key1 = Keypair::new();
|
||||
let parent = Arc::new(Bank::new(&genesis_config));
|
||||
@ -7852,7 +7957,7 @@ pub(crate) mod tests {
|
||||
let bank = new_from_parent(&parent);
|
||||
assert_eq!(
|
||||
bank.process_transaction(&tx),
|
||||
Err(TransactionError::DuplicateSignature)
|
||||
Err(TransactionError::AlreadyProcessed)
|
||||
);
|
||||
}
|
||||
|
||||
@ -9612,14 +9717,14 @@ pub(crate) mod tests {
|
||||
instructions,
|
||||
);
|
||||
let txs = vec![tx0, tx1];
|
||||
let batch = bank0.prepare_batch(&txs);
|
||||
let batch = bank0.prepare_batch(txs.iter());
|
||||
let balances = bank0.collect_balances(&batch);
|
||||
assert_eq!(balances.len(), 2);
|
||||
assert_eq!(balances[0], vec![8, 11, 1]);
|
||||
assert_eq!(balances[1], vec![8, 0, 1]);
|
||||
|
||||
let txs: Vec<_> = txs.iter().rev().cloned().collect();
|
||||
let batch = bank0.prepare_batch(&txs);
|
||||
let batch = bank0.prepare_batch(txs.iter());
|
||||
let balances = bank0.collect_balances(&batch);
|
||||
assert_eq!(balances.len(), 2);
|
||||
assert_eq!(balances[0], vec![8, 0, 1]);
|
||||
@ -9653,7 +9758,7 @@ pub(crate) mod tests {
|
||||
let tx2 = system_transaction::transfer(&keypair1, &pubkey2, 12, blockhash);
|
||||
let txs = vec![tx0, tx1, tx2];
|
||||
|
||||
let lock_result = bank0.prepare_batch(&txs);
|
||||
let lock_result = bank0.prepare_batch(txs.iter());
|
||||
let (transaction_results, transaction_balances_set, inner_instructions, transaction_logs) =
|
||||
bank0.load_execute_and_commit_transactions(
|
||||
&lock_result,
|
||||
@ -12088,7 +12193,7 @@ pub(crate) mod tests {
|
||||
let tx1 = system_transaction::transfer(&sender1, &recipient1, 110, blockhash); // Should produce insufficient funds log
|
||||
let failure_sig = tx1.signatures[0];
|
||||
let txs = vec![tx1, tx0];
|
||||
let batch = bank.prepare_batch(&txs);
|
||||
let batch = bank.prepare_batch(txs.iter());
|
||||
|
||||
let log_results = bank
|
||||
.load_execute_and_commit_transactions(
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
bank::{Bank, TransactionResults},
|
||||
genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs},
|
||||
hashed_transaction::HashedTransaction,
|
||||
vote_sender_types::ReplayVoteSender,
|
||||
};
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Signer, transaction::Transaction};
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Signer};
|
||||
use solana_vote_program::vote_transaction;
|
||||
|
||||
pub fn setup_bank_and_vote_pubkeys(num_vote_accounts: usize, stake: u64) -> (Bank, Vec<Pubkey>) {
|
||||
@ -27,7 +28,7 @@ pub fn setup_bank_and_vote_pubkeys(num_vote_accounts: usize, stake: u64) -> (Ban
|
||||
}
|
||||
|
||||
pub fn find_and_send_votes(
|
||||
txs: &[Transaction],
|
||||
hashed_txs: &[HashedTransaction],
|
||||
tx_results: &TransactionResults,
|
||||
vote_sender: Option<&ReplayVoteSender>,
|
||||
) {
|
||||
@ -41,7 +42,7 @@ pub fn find_and_send_votes(
|
||||
assert!(execution_results[old_account.transaction_result_index]
|
||||
.0
|
||||
.is_ok());
|
||||
let transaction = &txs[old_account.transaction_index];
|
||||
let transaction = hashed_txs[old_account.transaction_index].transaction();
|
||||
if let Some(parsed_vote) = vote_transaction::parse_vote_transaction(transaction) {
|
||||
if parsed_vote.1.slots.last().is_some() {
|
||||
let _ = vote_sender.send(parsed_vote);
|
||||
|
50
runtime/src/hashed_transaction.rs
Normal file
50
runtime/src/hashed_transaction.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use solana_sdk::{hash::Hash, transaction::Transaction};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Transaction and the hash of its message
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HashedTransaction<'a> {
|
||||
transaction: Cow<'a, Transaction>,
|
||||
pub message_hash: Hash,
|
||||
}
|
||||
|
||||
impl<'a> HashedTransaction<'a> {
|
||||
pub fn new(transaction: Cow<'a, Transaction>, message_hash: Hash) -> Self {
|
||||
Self {
|
||||
transaction,
|
||||
message_hash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transaction(&self) -> &Transaction {
|
||||
self.transaction.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Transaction> for HashedTransaction<'_> {
|
||||
fn from(transaction: Transaction) -> Self {
|
||||
Self {
|
||||
message_hash: transaction.message().hash(),
|
||||
transaction: Cow::Owned(transaction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Transaction> for HashedTransaction<'a> {
|
||||
fn from(transaction: &'a Transaction) -> Self {
|
||||
Self {
|
||||
message_hash: transaction.message().hash(),
|
||||
transaction: Cow::Borrowed(transaction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HashedTransactionSlice<'a> {
|
||||
fn as_transactions_iter(&'a self) -> Box<dyn Iterator<Item = &'a Transaction> + '_>;
|
||||
}
|
||||
|
||||
impl<'a> HashedTransactionSlice<'a> for [HashedTransaction<'a>] {
|
||||
fn as_transactions_iter(&'a self) -> Box<dyn Iterator<Item = &'a Transaction> + '_> {
|
||||
Box::new(self.iter().map(|h| h.transaction.as_ref()))
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ pub mod contains;
|
||||
pub mod epoch_stakes;
|
||||
pub mod genesis_utils;
|
||||
pub mod hardened_unpack;
|
||||
pub mod hashed_transaction;
|
||||
pub mod inline_spl_token_v2_0;
|
||||
pub mod instruction_recorder;
|
||||
pub mod loader_utils;
|
||||
|
@ -9,7 +9,6 @@ use solana_sdk::{
|
||||
};
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, HashSet},
|
||||
marker::PhantomData,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@ -42,27 +41,25 @@ pub struct SignatureConfirmationStatus<T> {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, AbiExample)]
|
||||
pub struct StatusCache<K, T: Serialize + Clone> {
|
||||
pub struct StatusCache<T: Serialize + Clone> {
|
||||
cache: KeyStatusMap<T>,
|
||||
roots: HashSet<Slot>,
|
||||
/// all keys seen during a fork/slot
|
||||
slot_deltas: SlotDeltaMap<T>,
|
||||
phantom: PhantomData<K>,
|
||||
}
|
||||
|
||||
impl<K, T: Serialize + Clone> Default for StatusCache<K, T> {
|
||||
impl<T: Serialize + Clone> Default for StatusCache<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cache: HashMap::default(),
|
||||
// 0 is always a root
|
||||
roots: [0].iter().cloned().collect(),
|
||||
slot_deltas: HashMap::default(),
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: AsRef<[u8]>, T: Serialize + Clone + PartialEq> PartialEq for StatusCache<K, T> {
|
||||
impl<T: Serialize + Clone + PartialEq> PartialEq for StatusCache<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.roots == other.roots
|
||||
&& self
|
||||
@ -88,7 +85,7 @@ impl<K: AsRef<[u8]>, T: Serialize + Clone + PartialEq> PartialEq for StatusCache
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: AsRef<[u8]>, T: Serialize + Clone> StatusCache<K, T> {
|
||||
impl<T: Serialize + Clone> StatusCache<T> {
|
||||
pub fn clear_slot_entries(&mut self, slot: Slot) {
|
||||
let slot_deltas = self.slot_deltas.remove(&slot);
|
||||
if let Some(slot_deltas) = slot_deltas {
|
||||
@ -131,17 +128,19 @@ impl<K: AsRef<[u8]>, T: Serialize + Clone> StatusCache<K, T> {
|
||||
|
||||
/// Check if the key is in any of the forks in the ancestors set and
|
||||
/// with a certain blockhash.
|
||||
pub fn get_status(
|
||||
pub fn get_status<K: AsRef<[u8]>>(
|
||||
&self,
|
||||
key: &K,
|
||||
key: K,
|
||||
transaction_blockhash: &Hash,
|
||||
ancestors: &Ancestors,
|
||||
) -> Option<(Slot, T)> {
|
||||
let map = self.cache.get(transaction_blockhash)?;
|
||||
let (_, index, keymap) = map;
|
||||
let mut key_slice = [0u8; CACHED_KEY_SIZE];
|
||||
key_slice.clone_from_slice(&key.as_ref()[*index..*index + CACHED_KEY_SIZE]);
|
||||
if let Some(stored_forks) = keymap.get(&key_slice) {
|
||||
let max_key_index = key.as_ref().len().saturating_sub(CACHED_KEY_SIZE + 1);
|
||||
let index = (*index).min(max_key_index);
|
||||
let key_slice: &[u8; CACHED_KEY_SIZE] =
|
||||
arrayref::array_ref![key.as_ref(), index, CACHED_KEY_SIZE];
|
||||
if let Some(stored_forks) = keymap.get(key_slice) {
|
||||
let res = stored_forks
|
||||
.iter()
|
||||
.find(|(f, _)| ancestors.get(f).is_some() || self.roots.get(f).is_some())
|
||||
@ -156,7 +155,11 @@ impl<K: AsRef<[u8]>, T: Serialize + Clone> StatusCache<K, T> {
|
||||
/// Search for a key with any blockhash
|
||||
/// Prefer get_status for performance reasons, it doesn't need
|
||||
/// to search all blockhashes.
|
||||
pub fn get_status_any_blockhash(&self, key: &K, ancestors: &Ancestors) -> Option<(Slot, T)> {
|
||||
pub fn get_status_any_blockhash<K: AsRef<[u8]>>(
|
||||
&self,
|
||||
key: &K,
|
||||
ancestors: &Ancestors,
|
||||
) -> Option<(Slot, T)> {
|
||||
let mut keys = vec![];
|
||||
let mut val: Vec<_> = self.cache.iter().map(|(k, _)| *k).collect();
|
||||
keys.append(&mut val);
|
||||
@ -183,22 +186,23 @@ impl<K: AsRef<[u8]>, T: Serialize + Clone> StatusCache<K, T> {
|
||||
}
|
||||
|
||||
/// Insert a new key for a specific slot.
|
||||
pub fn insert(&mut self, transaction_blockhash: &Hash, key: &K, slot: Slot, res: T) {
|
||||
let key_index: usize;
|
||||
if let Some(hash_map) = self.cache.get(transaction_blockhash) {
|
||||
key_index = hash_map.1;
|
||||
} else {
|
||||
key_index = thread_rng().gen_range(0, std::mem::size_of::<K>() - CACHED_KEY_SIZE);
|
||||
}
|
||||
pub fn insert<K: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
transaction_blockhash: &Hash,
|
||||
key: &K,
|
||||
slot: Slot,
|
||||
res: T,
|
||||
) {
|
||||
let max_key_index = key.as_ref().len().saturating_sub(CACHED_KEY_SIZE + 1);
|
||||
let hash_map = self.cache.entry(*transaction_blockhash).or_insert_with(|| {
|
||||
let key_index = thread_rng().gen_range(0, max_key_index + 1);
|
||||
(slot, key_index, HashMap::new())
|
||||
});
|
||||
|
||||
let hash_map =
|
||||
self.cache
|
||||
.entry(*transaction_blockhash)
|
||||
.or_insert((slot, key_index, HashMap::new()));
|
||||
hash_map.0 = std::cmp::max(slot, hash_map.0);
|
||||
let index = hash_map.1;
|
||||
let key_index = hash_map.1.min(max_key_index);
|
||||
let mut key_slice = [0u8; CACHED_KEY_SIZE];
|
||||
key_slice.clone_from_slice(&key.as_ref()[index..index + CACHED_KEY_SIZE]);
|
||||
key_slice.clone_from_slice(&key.as_ref()[key_index..key_index + CACHED_KEY_SIZE]);
|
||||
self.insert_with_slice(transaction_blockhash, slot, key_index, key_slice, res);
|
||||
}
|
||||
|
||||
@ -293,7 +297,7 @@ mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::{hash::hash, signature::Signature};
|
||||
|
||||
type BankStatusCache = StatusCache<Signature, ()>;
|
||||
type BankStatusCache = StatusCache<()>;
|
||||
|
||||
#[test]
|
||||
fn test_empty_has_no_sigs() {
|
||||
@ -418,9 +422,9 @@ mod tests {
|
||||
status_cache.clear();
|
||||
status_cache.insert(&blockhash, &sig, 0, ());
|
||||
let (_, index, sig_map) = status_cache.cache.get(&blockhash).unwrap();
|
||||
let mut sig_slice = [0u8; CACHED_KEY_SIZE];
|
||||
sig_slice.clone_from_slice(&sig.as_ref()[*index..*index + CACHED_KEY_SIZE]);
|
||||
assert!(sig_map.get(&sig_slice).is_some());
|
||||
let sig_slice: &[u8; CACHED_KEY_SIZE] =
|
||||
arrayref::array_ref![sig.as_ref(), *index, CACHED_KEY_SIZE];
|
||||
assert!(sig_map.get(sig_slice).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -510,4 +514,26 @@ mod tests {
|
||||
.is_none());
|
||||
assert!(status_cache.cache.is_empty());
|
||||
}
|
||||
|
||||
// Status cache uses a random key offset for each blockhash. Ensure that shorter
|
||||
// keys can still be used if the offset if greater than the key length.
|
||||
#[test]
|
||||
fn test_different_sized_keys() {
|
||||
let mut status_cache = BankStatusCache::default();
|
||||
let ancestors = vec![(0, 0)].into_iter().collect();
|
||||
let blockhash = Hash::default();
|
||||
for _ in 0..100 {
|
||||
let blockhash = hash(blockhash.as_ref());
|
||||
let sig_key = Signature::default();
|
||||
let hash_key = Hash::new_unique();
|
||||
status_cache.insert(&blockhash, &sig_key, 0, ());
|
||||
status_cache.insert(&blockhash, &hash_key, 0, ());
|
||||
assert!(status_cache
|
||||
.get_status(&sig_key, &blockhash, &ancestors)
|
||||
.is_some());
|
||||
assert!(status_cache
|
||||
.get_status(&hash_key, &blockhash, &ancestors)
|
||||
.is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
use crate::bank::Bank;
|
||||
use crate::hashed_transaction::HashedTransaction;
|
||||
use solana_sdk::transaction::{Result, Transaction};
|
||||
use std::borrow::Cow;
|
||||
|
||||
// Represents the results of trying to lock a set of accounts
|
||||
pub struct TransactionBatch<'a, 'b> {
|
||||
lock_results: Vec<Result<()>>,
|
||||
bank: &'a Bank,
|
||||
transactions: &'b [Transaction],
|
||||
hashed_txs: Cow<'b, [HashedTransaction<'b>]>,
|
||||
pub(crate) needs_unlock: bool,
|
||||
}
|
||||
|
||||
@ -13,13 +15,13 @@ impl<'a, 'b> TransactionBatch<'a, 'b> {
|
||||
pub fn new(
|
||||
lock_results: Vec<Result<()>>,
|
||||
bank: &'a Bank,
|
||||
transactions: &'b [Transaction],
|
||||
hashed_txs: Cow<'b, [HashedTransaction<'b>]>,
|
||||
) -> Self {
|
||||
assert_eq!(lock_results.len(), transactions.len());
|
||||
assert_eq!(lock_results.len(), hashed_txs.len());
|
||||
Self {
|
||||
lock_results,
|
||||
bank,
|
||||
transactions,
|
||||
hashed_txs,
|
||||
needs_unlock: true,
|
||||
}
|
||||
}
|
||||
@ -28,8 +30,12 @@ impl<'a, 'b> TransactionBatch<'a, 'b> {
|
||||
&self.lock_results
|
||||
}
|
||||
|
||||
pub fn transactions(&self) -> &[Transaction] {
|
||||
self.transactions
|
||||
pub fn hashed_transactions(&self) -> &[HashedTransaction] {
|
||||
&self.hashed_txs
|
||||
}
|
||||
|
||||
pub fn transactions_iter(&self) -> impl Iterator<Item = &Transaction> {
|
||||
self.hashed_txs.iter().map(|h| h.transaction())
|
||||
}
|
||||
|
||||
pub fn bank(&self) -> &Bank {
|
||||
@ -55,20 +61,20 @@ mod tests {
|
||||
let (bank, txs) = setup();
|
||||
|
||||
// Test getting locked accounts
|
||||
let batch = bank.prepare_batch(&txs);
|
||||
let batch = bank.prepare_batch(txs.iter());
|
||||
|
||||
// Grab locks
|
||||
assert!(batch.lock_results().iter().all(|x| x.is_ok()));
|
||||
|
||||
// Trying to grab locks again should fail
|
||||
let batch2 = bank.prepare_batch(&txs);
|
||||
let batch2 = bank.prepare_batch(txs.iter());
|
||||
assert!(batch2.lock_results().iter().all(|x| x.is_err()));
|
||||
|
||||
// Drop the first set of locks
|
||||
drop(batch);
|
||||
|
||||
// Now grabbing locks should work again
|
||||
let batch2 = bank.prepare_batch(&txs);
|
||||
let batch2 = bank.prepare_batch(txs.iter());
|
||||
assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
|
||||
}
|
||||
|
||||
@ -81,7 +87,7 @@ mod tests {
|
||||
assert!(batch.lock_results().iter().all(|x| x.is_ok()));
|
||||
|
||||
// Grab locks
|
||||
let batch2 = bank.prepare_batch(&txs);
|
||||
let batch2 = bank.prepare_batch(txs.iter());
|
||||
assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
|
||||
|
||||
// Prepare another batch without locks
|
||||
|
Reference in New Issue
Block a user