use {
crate::bank::Bank, core::ops::Deref, solana_sdk::transaction::Result,
solana_svm_transaction::svm_message::SVMMessage,
};
pub enum OwnedOrBorrowed<'a, T> {
Owned(Vec<T>),
Borrowed(&'a [T]),
}
impl<T> Deref for OwnedOrBorrowed<'_, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
match self {
OwnedOrBorrowed::Owned(v) => v,
OwnedOrBorrowed::Borrowed(v) => v,
}
}
}
pub struct TransactionBatch<'a, 'b, Tx: SVMMessage> {
lock_results: Vec<Result<()>>,
bank: &'a Bank,
sanitized_txs: OwnedOrBorrowed<'b, Tx>,
needs_unlock: bool,
}
impl<'a, 'b, Tx: SVMMessage> TransactionBatch<'a, 'b, Tx> {
pub fn new(
lock_results: Vec<Result<()>>,
bank: &'a Bank,
sanitized_txs: OwnedOrBorrowed<'b, Tx>,
) -> Self {
assert_eq!(lock_results.len(), sanitized_txs.len());
Self {
lock_results,
bank,
sanitized_txs,
needs_unlock: true,
}
}
pub fn lock_results(&self) -> &Vec<Result<()>> {
&self.lock_results
}
pub fn sanitized_transactions(&self) -> &[Tx] {
&self.sanitized_txs
}
pub fn bank(&self) -> &Bank {
self.bank
}
pub fn set_needs_unlock(&mut self, needs_unlock: bool) {
self.needs_unlock = needs_unlock;
}
pub fn needs_unlock(&self) -> bool {
self.needs_unlock
}
pub fn unlock_failures(&mut self, transaction_results: Vec<Result<()>>) {
assert_eq!(self.lock_results.len(), transaction_results.len());
if !self.needs_unlock() {
return;
}
let txs_and_results = transaction_results
.iter()
.enumerate()
.inspect(|(index, result)| {
assert!(!(result.is_ok() && self.lock_results[*index].is_err()))
})
.filter(|(index, result)| result.is_err() && self.lock_results[*index].is_ok())
.map(|(index, _)| (&self.sanitized_txs[index], &self.lock_results[index]));
self.bank.unlock_accounts(txs_and_results);
self.lock_results = transaction_results;
}
}
impl<'a, 'b, Tx: SVMMessage> Drop for TransactionBatch<'a, 'b, Tx> {
fn drop(&mut self) {
if self.needs_unlock() {
self.set_needs_unlock(false);
self.bank.unlock_accounts(
self.sanitized_transactions()
.iter()
.zip(self.lock_results()),
)
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo},
solana_sdk::{
signature::Keypair,
system_transaction,
transaction::{SanitizedTransaction, TransactionError},
},
};
#[test]
fn test_transaction_batch() {
let (bank, txs) = setup(false);
let batch = bank.prepare_sanitized_batch(&txs);
assert!(batch.lock_results().iter().all(|x| x.is_ok()));
let batch2 = bank.prepare_sanitized_batch(&txs);
assert!(batch2.lock_results().iter().all(|x| x.is_err()));
drop(batch);
let batch2 = bank.prepare_sanitized_batch(&txs);
assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
}
#[test]
fn test_simulation_batch() {
let (bank, txs) = setup(false);
let batch = bank.prepare_unlocked_batch_from_single_tx(&txs[0]);
assert!(batch.lock_results().iter().all(|x| x.is_ok()));
let batch2 = bank.prepare_sanitized_batch(&txs);
assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
let batch3 = bank.prepare_unlocked_batch_from_single_tx(&txs[0]);
assert!(batch3.lock_results().iter().all(|x| x.is_ok()));
}
#[test]
fn test_unlock_failures() {
let (bank, txs) = setup(true);
let mut batch = bank.prepare_sanitized_batch(&txs);
assert_eq!(
batch.lock_results,
vec![Ok(()), Err(TransactionError::AccountInUse), Ok(())]
);
let qos_results = vec![
Ok(()),
Err(TransactionError::AccountInUse),
Err(TransactionError::WouldExceedMaxBlockCostLimit),
];
batch.unlock_failures(qos_results.clone());
assert_eq!(batch.lock_results, qos_results);
drop(batch);
let batch2 = bank.prepare_sanitized_batch(&txs);
assert_eq!(
batch2.lock_results,
vec![Ok(()), Err(TransactionError::AccountInUse), Ok(())]
);
}
fn setup(insert_conflicting_tx: bool) -> (Bank, Vec<SanitizedTransaction>) {
let dummy_leader_pubkey = solana_sdk::pubkey::new_rand();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config_with_leader(500, &dummy_leader_pubkey, 100);
let bank = Bank::new_for_tests(&genesis_config);
let pubkey = solana_sdk::pubkey::new_rand();
let keypair2 = Keypair::new();
let pubkey2 = solana_sdk::pubkey::new_rand();
let mut txs = vec![SanitizedTransaction::from_transaction_for_tests(
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash()),
)];
if insert_conflicting_tx {
txs.push(SanitizedTransaction::from_transaction_for_tests(
system_transaction::transfer(&mint_keypair, &pubkey2, 1, genesis_config.hash()),
));
}
txs.push(SanitizedTransaction::from_transaction_for_tests(
system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_config.hash()),
));
(bank, txs)
}
}