Move transaction sanitization earlier in the pipeline (#18655)

* Move transaction sanitization earlier in the pipeline

* Renamed HashedTransaction to SanitizedTransaction

* Implement deref for sanitized transaction

* bring back process_transactions test method

* Use sanitized transactions for cost model calculation
This commit is contained in:
Justin Starry 2021-07-15 22:51:27 -05:00 committed by GitHub
parent c03490b24a
commit d166b9856a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 448 additions and 369 deletions

View File

@ -200,7 +200,8 @@ fn main() {
}); });
bank.clear_signatures(); bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel //sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions);
let res = bank.process_transactions(transactions.iter());
for r in res { for r in res {
assert!(r.is_ok(), "sanity parallel execution error: {:?}", r); assert!(r.is_ok(), "sanity parallel execution error: {:?}", r);
} }

View File

@ -70,7 +70,7 @@ impl BanksServer {
.map(|info| deserialize(&info.wire_transaction).unwrap()) .map(|info| deserialize(&info.wire_transaction).unwrap())
.collect(); .collect();
let bank = bank_forks.read().unwrap().working_bank(); let bank = bank_forks.read().unwrap().working_bank();
let _ = bank.process_transactions(&transactions); let _ = bank.try_process_transactions(transactions.iter());
} }
} }

View File

@ -193,7 +193,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
}); });
bank.clear_signatures(); bank.clear_signatures();
//sanity check, make sure all the transactions can execute in parallel //sanity check, make sure all the transactions can execute in parallel
let res = bank.process_transactions(&transactions); let res = bank.process_transactions(transactions.iter());
for r in res { for r in res {
assert!(r.is_ok(), "sanity parallel execution"); assert!(r.is_ok(), "sanity parallel execution");
} }

View File

@ -27,7 +27,6 @@ use solana_runtime::{
transaction_batch::TransactionBatch, transaction_batch::TransactionBatch,
vote_sender_types::ReplayVoteSender, vote_sender_types::ReplayVoteSender,
}; };
use solana_sdk::hashed_transaction::HashedTransaction;
use solana_sdk::{ use solana_sdk::{
clock::{ clock::{
Slot, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY, Slot, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY,
@ -35,6 +34,7 @@ use solana_sdk::{
}, },
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
sanitized_transaction::SanitizedTransaction,
short_vec::decode_shortu16_len, short_vec::decode_shortu16_len,
signature::Signature, signature::Signature,
timing::{duration_as_ms, timestamp}, timing::{duration_as_ms, timestamp},
@ -863,11 +863,11 @@ impl BankingStage {
record_time.stop(); record_time.stop();
let mut commit_time = Measure::start("commit_time"); let mut commit_time = Measure::start("commit_time");
let hashed_txs = batch.hashed_transactions(); let sanitized_txs = batch.sanitized_transactions();
let num_to_commit = num_to_commit.unwrap(); let num_to_commit = num_to_commit.unwrap();
if num_to_commit != 0 { if num_to_commit != 0 {
let tx_results = bank.commit_transactions( let tx_results = bank.commit_transactions(
hashed_txs, sanitized_txs,
&mut loaded_accounts, &mut loaded_accounts,
&results, &results,
tx_count, tx_count,
@ -875,7 +875,7 @@ impl BankingStage {
&mut execute_timings, &mut execute_timings,
); );
bank_utils::find_and_send_votes(hashed_txs, &tx_results, Some(gossip_vote_sender)); bank_utils::find_and_send_votes(sanitized_txs, &tx_results, Some(gossip_vote_sender));
if let Some(transaction_status_sender) = transaction_status_sender { if let Some(transaction_status_sender) = transaction_status_sender {
let txs = batch.transactions_iter().cloned().collect(); let txs = batch.transactions_iter().cloned().collect();
let post_balances = bank.collect_balances(batch); let post_balances = bank.collect_balances(batch);
@ -902,7 +902,7 @@ impl BankingStage {
load_execute_time.as_us(), load_execute_time.as_us(),
record_time.as_us(), record_time.as_us(),
commit_time.as_us(), commit_time.as_us(),
hashed_txs.len(), sanitized_txs.len(),
); );
debug!( debug!(
@ -915,7 +915,7 @@ impl BankingStage {
pub fn process_and_record_transactions( pub fn process_and_record_transactions(
bank: &Arc<Bank>, bank: &Arc<Bank>,
txs: &[HashedTransaction], txs: &[SanitizedTransaction],
poh: &TransactionRecorder, poh: &TransactionRecorder,
chunk_offset: usize, chunk_offset: usize,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
@ -924,7 +924,7 @@ impl BankingStage {
let mut lock_time = Measure::start("lock_time"); let mut lock_time = Measure::start("lock_time");
// Once accounts are locked, other threads cannot encode transactions that will modify the // Once accounts are locked, other threads cannot encode transactions that will modify the
// same account state // same account state
let batch = bank.prepare_hashed_batch(txs); let batch = bank.prepare_sanitized_batch(txs);
lock_time.stop(); lock_time.stop();
let (result, mut retryable_txs) = Self::process_and_record_transactions_locked( let (result, mut retryable_txs) = Self::process_and_record_transactions_locked(
@ -959,7 +959,7 @@ impl BankingStage {
fn process_transactions( fn process_transactions(
bank: &Arc<Bank>, bank: &Arc<Bank>,
bank_creation_time: &Instant, bank_creation_time: &Instant,
transactions: &[HashedTransaction], transactions: &[SanitizedTransaction],
poh: &TransactionRecorder, poh: &TransactionRecorder,
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
gossip_vote_sender: &ReplayVoteSender, gossip_vote_sender: &ReplayVoteSender,
@ -1062,7 +1062,7 @@ impl BankingStage {
libsecp256k1_0_5_upgrade_enabled: bool, libsecp256k1_0_5_upgrade_enabled: bool,
cost_tracker: &Arc<RwLock<CostTracker>>, cost_tracker: &Arc<RwLock<CostTracker>>,
banking_stage_stats: &BankingStageStats, banking_stage_stats: &BankingStageStats,
) -> (Vec<HashedTransaction<'static>>, Vec<usize>, Vec<usize>) { ) -> (Vec<SanitizedTransaction<'static>>, Vec<usize>, Vec<usize>) {
let mut retryable_transaction_packet_indexes: Vec<usize> = vec![]; let mut retryable_transaction_packet_indexes: Vec<usize> = vec![];
let verified_transactions_with_packet_indexes: Vec<_> = transaction_indexes let verified_transactions_with_packet_indexes: Vec<_> = transaction_indexes
@ -1072,6 +1072,9 @@ impl BankingStage {
let tx: Transaction = limited_deserialize(&p.data[0..p.meta.size]).ok()?; let tx: Transaction = limited_deserialize(&p.data[0..p.meta.size]).ok()?;
tx.verify_precompiles(libsecp256k1_0_5_upgrade_enabled) tx.verify_precompiles(libsecp256k1_0_5_upgrade_enabled)
.ok()?; .ok()?;
let message_bytes = Self::packet_message(p)?;
let message_hash = Message::hash_raw_message(message_bytes);
let tx = SanitizedTransaction::try_create(Cow::Owned(tx), message_hash).ok()?;
Some((tx, *tx_index)) Some((tx, *tx_index))
}) })
.collect(); .collect();
@ -1081,7 +1084,7 @@ impl BankingStage {
); );
let mut cost_tracker_check_time = Measure::start("cost_tracker_check_time"); let mut cost_tracker_check_time = Measure::start("cost_tracker_check_time");
let filtered_transactions_with_packet_indexes: Vec<_> = { let (filtered_transactions, filter_transaction_packet_indexes) = {
let cost_tracker_readonly = cost_tracker.read().unwrap(); let cost_tracker_readonly = cost_tracker.read().unwrap();
verified_transactions_with_packet_indexes verified_transactions_with_packet_indexes
.into_iter() .into_iter()
@ -1094,24 +1097,10 @@ impl BankingStage {
} }
Some((tx, tx_index)) Some((tx, tx_index))
}) })
.collect() .unzip()
}; };
cost_tracker_check_time.stop(); cost_tracker_check_time.stop();
let (filtered_transactions, filter_transaction_packet_indexes) =
filtered_transactions_with_packet_indexes
.into_iter()
.filter_map(|(tx, tx_index)| {
let p = &msgs.packets[tx_index];
let message_bytes = Self::packet_message(p)?;
let message_hash = Message::hash_raw_message(message_bytes);
Some((
HashedTransaction::new(Cow::Owned(tx), message_hash),
tx_index,
))
})
.unzip();
banking_stage_stats banking_stage_stats
.cost_tracker_check_elapsed .cost_tracker_check_elapsed
.fetch_add(cost_tracker_check_time.as_us(), Ordering::Relaxed); .fetch_add(cost_tracker_check_time.as_us(), Ordering::Relaxed);
@ -1130,7 +1119,7 @@ impl BankingStage {
/// * `pending_indexes` - identifies which indexes in the `transactions` list are still pending /// * `pending_indexes` - identifies which indexes in the `transactions` list are still pending
fn filter_pending_packets_from_pending_txs( fn filter_pending_packets_from_pending_txs(
bank: &Arc<Bank>, bank: &Arc<Bank>,
transactions: &[HashedTransaction], transactions: &[SanitizedTransaction],
transaction_to_packet_indexes: &[usize], transaction_to_packet_indexes: &[usize],
pending_indexes: &[usize], pending_indexes: &[usize],
) -> Vec<usize> { ) -> Vec<usize> {
@ -1218,17 +1207,7 @@ impl BankingStage {
let mut cost_tracking_time = Measure::start("cost_tracking_time"); let mut cost_tracking_time = Measure::start("cost_tracking_time");
transactions.iter().enumerate().for_each(|(index, tx)| { transactions.iter().enumerate().for_each(|(index, tx)| {
if unprocessed_tx_indexes.iter().all(|&i| i != index) { if unprocessed_tx_indexes.iter().all(|&i| i != index) {
cost_tracker cost_tracker.write().unwrap().add_transaction_cost(tx);
.write()
.unwrap()
.add_transaction_cost(tx.transaction())
.unwrap_or_else(|err| {
warn!(
"failed to track transaction cost, err {:?}, tx {:?}",
err,
tx.transaction()
)
});
} }
}); });
cost_tracking_time.stop(); cost_tracking_time.stop();
@ -1602,6 +1581,7 @@ mod tests {
}; };
use solana_transaction_status::TransactionWithStatusMeta; use solana_transaction_status::TransactionWithStatusMeta;
use std::{ use std::{
convert::TryInto,
net::SocketAddr, net::SocketAddr,
path::Path, path::Path,
sync::{ sync::{
@ -1817,7 +1797,7 @@ mod tests {
if !entries.is_empty() { if !entries.is_empty() {
blockhash = entries.last().unwrap().hash; blockhash = entries.last().unwrap().hash;
for entry in entries { for entry in entries {
bank.process_transactions(&entry.transactions) bank.process_transactions(entry.transactions.iter())
.iter() .iter()
.for_each(|x| assert_eq!(*x, Ok(()))); .for_each(|x| assert_eq!(*x, Ok(())));
} }
@ -1931,7 +1911,7 @@ mod tests {
let bank = Bank::new_no_wallclock_throttle(&genesis_config); let bank = Bank::new_no_wallclock_throttle(&genesis_config);
for entry in &entries { for entry in &entries {
bank.process_transactions(&entry.transactions) bank.process_transactions(entry.transactions.iter())
.iter() .iter()
.for_each(|x| assert_eq!(*x, Ok(()))); .for_each(|x| assert_eq!(*x, Ok(())));
} }
@ -2223,7 +2203,8 @@ mod tests {
let transactions = let transactions =
vec![ vec![
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash()) system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash())
.into(), .try_into()
.unwrap(),
]; ];
let start = Arc::new(Instant::now()); let start = Arc::new(Instant::now());
@ -2292,7 +2273,8 @@ mod tests {
2, 2,
genesis_config.hash(), genesis_config.hash(),
) )
.into()]; .try_into()
.unwrap()];
assert_matches!( assert_matches!(
BankingStage::process_and_record_transactions( BankingStage::process_and_record_transactions(
@ -2353,8 +2335,12 @@ mod tests {
let pubkey1 = solana_sdk::pubkey::new_rand(); let pubkey1 = solana_sdk::pubkey::new_rand();
let transactions = vec![ let transactions = vec![
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash()).into(), system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash())
system_transaction::transfer(&mint_keypair, &pubkey1, 1, genesis_config.hash()).into(), .try_into()
.unwrap(),
system_transaction::transfer(&mint_keypair, &pubkey1, 1, genesis_config.hash())
.try_into()
.unwrap(),
]; ];
let start = Arc::new(Instant::now()); let start = Arc::new(Instant::now());
@ -2467,7 +2453,8 @@ mod tests {
let transactions = let transactions =
vec![ vec![
system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash()) system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash())
.into(), .try_into()
.unwrap(),
]; ];
let ledger_path = get_tmp_ledger_path!(); let ledger_path = get_tmp_ledger_path!();
@ -2544,7 +2531,11 @@ mod tests {
let entry_3 = next_entry(&entry_2.hash, 1, vec![fail_tx.clone()]); let entry_3 = next_entry(&entry_2.hash, 1, vec![fail_tx.clone()]);
let entries = vec![entry_1, entry_2, entry_3]; let entries = vec![entry_1, entry_2, entry_3];
let transactions = vec![success_tx.into(), ix_error_tx.into(), fail_tx.into()]; let transactions = vec![
success_tx.try_into().unwrap(),
ix_error_tx.try_into().unwrap(),
fail_tx.try_into().unwrap(),
];
bank.transfer(4, &mint_keypair, &keypair1.pubkey()).unwrap(); bank.transfer(4, &mint_keypair, &keypair1.pubkey()).unwrap();
let start = Arc::new(Instant::now()); let start = Arc::new(Instant::now());

View File

@ -9,7 +9,7 @@
//! //!
use crate::execute_cost_table::ExecuteCostTable; use crate::execute_cost_table::ExecuteCostTable;
use log::*; use log::*;
use solana_sdk::{pubkey::Pubkey, transaction::Transaction}; use solana_sdk::{pubkey::Pubkey, sanitized_transaction::SanitizedTransaction};
use std::collections::HashMap; use std::collections::HashMap;
// Guestimated from mainnet-beta data, sigver averages 1us, average read 7us and average write 25us // Guestimated from mainnet-beta data, sigver averages 1us, average read 7us and average write 25us
@ -126,14 +126,11 @@ impl CostModel {
); );
} }
pub fn calculate_cost( pub fn calculate_cost(&mut self, transaction: &SanitizedTransaction) -> &TransactionCost {
&mut self,
transaction: &Transaction,
) -> Result<&TransactionCost, CostModelError> {
self.transaction_cost.reset(); self.transaction_cost.reset();
// calculate transaction exeution cost // calculate transaction exeution cost
self.transaction_cost.execution_cost = self.find_transaction_cost(transaction)?; self.transaction_cost.execution_cost = self.find_transaction_cost(transaction);
// calculate account access cost // calculate account access cost
let message = transaction.message(); let message = transaction.message();
@ -159,7 +156,7 @@ impl CostModel {
"transaction {:?} has cost {:?}", "transaction {:?} has cost {:?}",
transaction, self.transaction_cost transaction, self.transaction_cost
); );
Ok(&self.transaction_cost) &self.transaction_cost
} }
// To update or insert instruction cost to table. // To update or insert instruction cost to table.
@ -194,15 +191,10 @@ impl CostModel {
} }
} }
fn find_transaction_cost(&self, transaction: &Transaction) -> Result<u64, CostModelError> { fn find_transaction_cost(&self, transaction: &SanitizedTransaction) -> u64 {
let mut cost: u64 = 0; let mut cost: u64 = 0;
for instruction in &transaction.message().instructions { for instruction in &transaction.message().instructions {
// The Transaction may not be sanitized at this point
if instruction.program_id_index as usize >= transaction.message().account_keys.len() {
return Err(CostModelError::InvalidTransaction);
}
let program_id = let program_id =
transaction.message().account_keys[instruction.program_id_index as usize]; transaction.message().account_keys[instruction.program_id_index as usize];
let instruction_cost = self.find_instruction_cost(&program_id); let instruction_cost = self.find_instruction_cost(&program_id);
@ -213,7 +205,7 @@ impl CostModel {
); );
cost += instruction_cost; cost += instruction_cost;
} }
Ok(cost) cost
} }
} }
@ -232,8 +224,10 @@ mod tests {
signature::{Keypair, Signer}, signature::{Keypair, Signer},
system_instruction::{self}, system_instruction::{self},
system_program, system_transaction, system_program, system_transaction,
transaction::Transaction,
}; };
use std::{ use std::{
convert::{TryFrom, TryInto},
str::FromStr, str::FromStr,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
thread::{self, JoinHandle}, thread::{self, JoinHandle},
@ -279,8 +273,10 @@ mod tests {
let (mint_keypair, start_hash) = test_setup(); let (mint_keypair, start_hash) = test_setup();
let keypair = Keypair::new(); let keypair = Keypair::new();
let simple_transaction = let simple_transaction: SanitizedTransaction =
system_transaction::transfer(&mint_keypair, &keypair.pubkey(), 2, start_hash); system_transaction::transfer(&mint_keypair, &keypair.pubkey(), 2, start_hash)
.try_into()
.unwrap();
debug!( debug!(
"system_transaction simple_transaction {:?}", "system_transaction simple_transaction {:?}",
simple_transaction simple_transaction
@ -295,7 +291,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
expected_cost, expected_cost,
testee.find_transaction_cost(&simple_transaction).unwrap() testee.find_transaction_cost(&simple_transaction)
); );
} }
@ -308,7 +304,9 @@ mod tests {
let instructions = let instructions =
system_instruction::transfer_many(&mint_keypair.pubkey(), &[(key1, 1), (key2, 1)]); system_instruction::transfer_many(&mint_keypair.pubkey(), &[(key1, 1), (key2, 1)]);
let message = Message::new(&instructions, Some(&mint_keypair.pubkey())); let message = Message::new(&instructions, Some(&mint_keypair.pubkey()));
let tx = Transaction::new(&[&mint_keypair], message, start_hash); let tx: SanitizedTransaction = Transaction::new(&[&mint_keypair], message, start_hash)
.try_into()
.unwrap();
debug!("many transfer transaction {:?}", tx); debug!("many transfer transaction {:?}", tx);
// expected cost for two system transfer instructions // expected cost for two system transfer instructions
@ -319,7 +317,7 @@ mod tests {
testee testee
.upsert_instruction_cost(&system_program::id(), program_cost) .upsert_instruction_cost(&system_program::id(), program_cost)
.unwrap(); .unwrap();
assert_eq!(expected_cost, testee.find_transaction_cost(&tx).unwrap()); assert_eq!(expected_cost, testee.find_transaction_cost(&tx));
} }
#[test] #[test]
@ -335,17 +333,19 @@ mod tests {
CompiledInstruction::new(3, &(), vec![0, 1]), CompiledInstruction::new(3, &(), vec![0, 1]),
CompiledInstruction::new(4, &(), vec![0, 2]), CompiledInstruction::new(4, &(), vec![0, 2]),
]; ];
let tx = Transaction::new_with_compiled_instructions( let tx: SanitizedTransaction = Transaction::new_with_compiled_instructions(
&[&mint_keypair], &[&mint_keypair],
&[key1, key2], &[key1, key2],
start_hash, start_hash,
vec![prog1, prog2], vec![prog1, prog2],
instructions, instructions,
); )
.try_into()
.unwrap();
debug!("many random transaction {:?}", tx); debug!("many random transaction {:?}", tx);
let testee = CostModel::default(); let testee = CostModel::default();
let result = testee.find_transaction_cost(&tx).unwrap(); let result = testee.find_transaction_cost(&tx);
// expected cost for two random/unknown program is // expected cost for two random/unknown program is
let expected_cost = testee.instruction_execution_cost_table.get_mode() * 2; let expected_cost = testee.instruction_execution_cost_table.get_mode() * 2;
@ -365,16 +365,18 @@ mod tests {
CompiledInstruction::new(4, &(), vec![0, 2]), CompiledInstruction::new(4, &(), vec![0, 2]),
CompiledInstruction::new(5, &(), vec![1, 3]), CompiledInstruction::new(5, &(), vec![1, 3]),
]; ];
let tx = Transaction::new_with_compiled_instructions( let tx: SanitizedTransaction = Transaction::new_with_compiled_instructions(
&[&signer1, &signer2], &[&signer1, &signer2],
&[key1, key2], &[key1, key2],
Hash::new_unique(), Hash::new_unique(),
vec![prog1, prog2], vec![prog1, prog2],
instructions, instructions,
); )
.try_into()
.unwrap();
let mut cost_model = CostModel::default(); let mut cost_model = CostModel::default();
let tx_cost = cost_model.calculate_cost(&tx).unwrap(); let tx_cost = cost_model.calculate_cost(&tx);
assert_eq!(2 + 2, tx_cost.writable_accounts.len()); assert_eq!(2 + 2, tx_cost.writable_accounts.len());
assert_eq!(signer1.pubkey(), tx_cost.writable_accounts[0]); assert_eq!(signer1.pubkey(), tx_cost.writable_accounts[0]);
assert_eq!(signer2.pubkey(), tx_cost.writable_accounts[1]); assert_eq!(signer2.pubkey(), tx_cost.writable_accounts[1]);
@ -404,8 +406,10 @@ mod tests {
#[test] #[test]
fn test_cost_model_calculate_cost() { fn test_cost_model_calculate_cost() {
let (mint_keypair, start_hash) = test_setup(); let (mint_keypair, start_hash) = test_setup();
let tx = let tx: SanitizedTransaction =
system_transaction::transfer(&mint_keypair, &Keypair::new().pubkey(), 2, start_hash); system_transaction::transfer(&mint_keypair, &Keypair::new().pubkey(), 2, start_hash)
.try_into()
.unwrap();
let expected_account_cost = SIGNED_WRITABLE_ACCOUNT_ACCESS_COST let expected_account_cost = SIGNED_WRITABLE_ACCOUNT_ACCESS_COST
+ NON_SIGNED_WRITABLE_ACCOUNT_ACCESS_COST + NON_SIGNED_WRITABLE_ACCOUNT_ACCESS_COST
@ -416,7 +420,7 @@ mod tests {
cost_model cost_model
.upsert_instruction_cost(&system_program::id(), expected_execution_cost) .upsert_instruction_cost(&system_program::id(), expected_execution_cost)
.unwrap(); .unwrap();
let tx_cost = cost_model.calculate_cost(&tx).unwrap(); let tx_cost = cost_model.calculate_cost(&tx);
assert_eq!(expected_account_cost, tx_cost.account_access_cost); assert_eq!(expected_account_cost, tx_cost.account_access_cost);
assert_eq!(expected_execution_cost, tx_cost.execution_cost); assert_eq!(expected_execution_cost, tx_cost.execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len()); assert_eq!(2, tx_cost.writable_accounts.len());
@ -452,13 +456,16 @@ mod tests {
CompiledInstruction::new(3, &(), vec![0, 1]), CompiledInstruction::new(3, &(), vec![0, 1]),
CompiledInstruction::new(4, &(), vec![0, 2]), CompiledInstruction::new(4, &(), vec![0, 2]),
]; ];
let tx = Arc::new(Transaction::new_with_compiled_instructions( let tx = Arc::new(
&[&mint_keypair], SanitizedTransaction::try_from(Transaction::new_with_compiled_instructions(
&[key1, key2], &[&mint_keypair],
start_hash, &[key1, key2],
vec![prog1, prog2], start_hash,
instructions, vec![prog1, prog2],
)); instructions,
))
.unwrap(),
);
let number_threads = 10; let number_threads = 10;
let expected_account_cost = SIGNED_WRITABLE_ACCOUNT_ACCESS_COST let expected_account_cost = SIGNED_WRITABLE_ACCOUNT_ACCESS_COST
@ -484,7 +491,7 @@ mod tests {
} else { } else {
thread::spawn(move || { thread::spawn(move || {
let mut cost_model = cost_model.write().unwrap(); let mut cost_model = cost_model.write().unwrap();
let tx_cost = cost_model.calculate_cost(&tx).unwrap(); let tx_cost = cost_model.calculate_cost(&tx);
assert_eq!(3, tx_cost.writable_accounts.len()); assert_eq!(3, tx_cost.writable_accounts.len());
assert_eq!(expected_account_cost, tx_cost.account_access_cost); assert_eq!(expected_account_cost, tx_cost.account_access_cost);
}) })

View File

@ -5,7 +5,7 @@
//! - add_transaction_cost(&tx), mutable function to accumulate `tx` cost to tracker. //! - add_transaction_cost(&tx), mutable function to accumulate `tx` cost to tracker.
//! //!
use crate::cost_model::{CostModel, CostModelError, TransactionCost}; use crate::cost_model::{CostModel, CostModelError, TransactionCost};
use solana_sdk::{clock::Slot, pubkey::Pubkey, transaction::Transaction}; use solana_sdk::{clock::Slot, pubkey::Pubkey, sanitized_transaction::SanitizedTransaction};
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
@ -43,21 +43,21 @@ impl CostTracker {
} }
} }
pub fn would_transaction_fit(&self, transaction: &Transaction) -> Result<(), CostModelError> { pub fn would_transaction_fit(
&self,
transaction: &SanitizedTransaction,
) -> Result<(), CostModelError> {
let mut cost_model = self.cost_model.write().unwrap(); let mut cost_model = self.cost_model.write().unwrap();
let tx_cost = cost_model.calculate_cost(transaction)?; let tx_cost = cost_model.calculate_cost(transaction);
self.would_fit( self.would_fit(
&tx_cost.writable_accounts, &tx_cost.writable_accounts,
&(tx_cost.account_access_cost + tx_cost.execution_cost), &(tx_cost.account_access_cost + tx_cost.execution_cost),
) )
} }
pub fn add_transaction_cost( pub fn add_transaction_cost(&mut self, transaction: &SanitizedTransaction) {
&mut self,
transaction: &Transaction,
) -> Result<(), CostModelError> {
let mut cost_model = self.cost_model.write().unwrap(); let mut cost_model = self.cost_model.write().unwrap();
let tx_cost = cost_model.calculate_cost(transaction)?; let tx_cost = cost_model.calculate_cost(transaction);
let cost = tx_cost.account_access_cost + tx_cost.execution_cost; let cost = tx_cost.account_access_cost + tx_cost.execution_cost;
for account_key in tx_cost.writable_accounts.iter() { for account_key in tx_cost.writable_accounts.iter() {
*self *self
@ -66,7 +66,6 @@ impl CostTracker {
.or_insert(0) += cost; .or_insert(0) += cost;
} }
self.block_cost += cost; self.block_cost += cost;
Ok(())
} }
pub fn reset_if_new_bank(&mut self, slot: Slot) { pub fn reset_if_new_bank(&mut self, slot: Slot) {

View File

@ -18,12 +18,13 @@ use solana_perf::perf_libs;
use solana_perf::recycler::Recycler; use solana_perf::recycler::Recycler;
use solana_rayon_threadlimit::get_thread_count; use solana_rayon_threadlimit::get_thread_count;
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::hashed_transaction::HashedTransaction;
use solana_sdk::packet::PACKET_DATA_SIZE; use solana_sdk::packet::PACKET_DATA_SIZE;
use solana_sdk::sanitized_transaction::SanitizedTransaction;
use solana_sdk::timing; use solana_sdk::timing;
use solana_sdk::transaction::Transaction; use solana_sdk::transaction::{Result, Transaction, TransactionError};
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::TryFrom;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::sync::Once; use std::sync::Once;
@ -122,22 +123,23 @@ pub struct Entry {
/// Typed entry to distinguish between transaction and tick entries /// Typed entry to distinguish between transaction and tick entries
pub enum EntryType<'a> { pub enum EntryType<'a> {
Transactions(Vec<HashedTransaction<'a>>), Transactions(Vec<SanitizedTransaction<'a>>),
Tick(Hash), Tick(Hash),
} }
impl<'a> From<&'a Entry> for EntryType<'a> { impl<'a> TryFrom<&'a Entry> for EntryType<'a> {
fn from(entry: &'a Entry) -> Self { type Error = TransactionError;
fn try_from(entry: &'a Entry) -> Result<Self> {
if entry.transactions.is_empty() { if entry.transactions.is_empty() {
EntryType::Tick(entry.hash) Ok(EntryType::Tick(entry.hash))
} else { } else {
EntryType::Transactions( Ok(EntryType::Transactions(
entry entry
.transactions .transactions
.iter() .iter()
.map(HashedTransaction::from) .map(SanitizedTransaction::try_from)
.collect(), .collect::<Result<_>>()?,
) ))
} }
} }
} }
@ -361,7 +363,7 @@ pub trait EntrySlice {
skip_verification: bool, skip_verification: bool,
libsecp256k1_0_5_upgrade_enabled: bool, libsecp256k1_0_5_upgrade_enabled: bool,
verify_tx_signatures_len: bool, verify_tx_signatures_len: bool,
) -> Option<Vec<EntryType<'_>>>; ) -> Result<Vec<EntryType<'_>>>;
} }
impl EntrySlice for [Entry] { impl EntrySlice for [Entry] {
@ -517,24 +519,24 @@ impl EntrySlice for [Entry] {
skip_verification: bool, skip_verification: bool,
libsecp256k1_0_5_upgrade_enabled: bool, libsecp256k1_0_5_upgrade_enabled: bool,
verify_tx_signatures_len: bool, verify_tx_signatures_len: bool,
) -> Option<Vec<EntryType<'a>>> { ) -> Result<Vec<EntryType<'a>>> {
let verify_and_hash = |tx: &'a Transaction| -> Option<HashedTransaction<'a>> { let verify_and_hash = |tx: &'a Transaction| -> Result<SanitizedTransaction<'a>> {
let message_hash = if !skip_verification { let message_hash = if !skip_verification {
let size = bincode::serialized_size(tx).ok()?; let size =
bincode::serialized_size(tx).map_err(|_| TransactionError::SanitizeFailure)?;
if size > PACKET_DATA_SIZE as u64 { if size > PACKET_DATA_SIZE as u64 {
return None; return Err(TransactionError::SanitizeFailure);
} }
tx.verify_precompiles(libsecp256k1_0_5_upgrade_enabled) tx.verify_precompiles(libsecp256k1_0_5_upgrade_enabled)?;
.ok()?;
if verify_tx_signatures_len && !tx.verify_signatures_len() { if verify_tx_signatures_len && !tx.verify_signatures_len() {
return None; return Err(TransactionError::SanitizeFailure);
} }
tx.verify_and_hash_message().ok()? tx.verify_and_hash_message()?
} else { } else {
tx.message().hash() tx.message().hash()
}; };
Some(HashedTransaction::new(Cow::Borrowed(tx), message_hash)) SanitizedTransaction::try_create(Cow::Borrowed(tx), message_hash)
}; };
PAR_THREAD_POOL.with(|thread_pool| { PAR_THREAD_POOL.with(|thread_pool| {
@ -542,14 +544,14 @@ impl EntrySlice for [Entry] {
self.par_iter() self.par_iter()
.map(|entry| { .map(|entry| {
if entry.transactions.is_empty() { if entry.transactions.is_empty() {
Some(EntryType::Tick(entry.hash)) Ok(EntryType::Tick(entry.hash))
} else { } else {
Some(EntryType::Transactions( Ok(EntryType::Transactions(
entry entry
.transactions .transactions
.par_iter() .par_iter()
.map(verify_and_hash) .map(verify_and_hash)
.collect::<Option<Vec<HashedTransaction>>>()?, .collect::<Result<Vec<_>>>()?,
)) ))
} }
}) })
@ -920,27 +922,66 @@ mod tests {
} }
tx tx
}; };
// No signatures. // Too few signatures: Sanitization failure
{ {
let tx = make_transaction(TestCase::RemoveSignature); let tx = make_transaction(TestCase::RemoveSignature);
let entries = vec![next_entry(&recent_blockhash, 1, vec![tx])]; let entries = vec![next_entry(&recent_blockhash, 1, vec![tx])];
assert!(entries[..] assert_eq!(
.verify_and_hash_transactions(false, false, false) entries[..]
.is_some()); .verify_and_hash_transactions(false, false, false)
assert!(entries[..] .err(),
.verify_and_hash_transactions(false, false, true) Some(TransactionError::SanitizeFailure),
.is_none()); );
} }
// Too many signatures. // Too many signatures: Sanitize failure only with feature switch
{ {
let tx = make_transaction(TestCase::AddSignature); let tx = make_transaction(TestCase::AddSignature);
let entries = vec![next_entry(&recent_blockhash, 1, vec![tx])]; let entries = vec![next_entry(&recent_blockhash, 1, vec![tx])];
assert!(entries[..] assert!(entries[..]
.verify_and_hash_transactions(false, false, false) .verify_and_hash_transactions(false, false, false)
.is_some()); .is_ok());
assert!(entries[..] assert_eq!(
.verify_and_hash_transactions(false, false, true) entries[..]
.is_none()); .verify_and_hash_transactions(false, false, true)
.err(),
Some(TransactionError::SanitizeFailure)
);
}
}
#[test]
fn test_verify_and_hash_transactions_load_duplicate_account() {
let mut rng = rand::thread_rng();
let recent_blockhash = hash_new_rand(&mut rng);
let from_keypair = Keypair::new();
let to_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let to_pubkey = to_keypair.pubkey();
let make_transaction = || {
let mut message = Message::new(
&[system_instruction::transfer(&from_pubkey, &to_pubkey, 1)],
Some(&from_pubkey),
);
let to_index = message
.account_keys
.iter()
.position(|k| k == &to_pubkey)
.unwrap();
message.account_keys[to_index] = from_pubkey;
Transaction::new(&[&from_keypair], message, recent_blockhash)
};
// Duplicate account
{
let tx = make_transaction();
let entries = vec![next_entry(&recent_blockhash, 1, vec![tx])];
assert_eq!(
entries[..]
.verify_and_hash_transactions(false, false, false)
.err(),
Some(TransactionError::AccountLoadedTwice)
);
} }
} }
@ -966,16 +1007,19 @@ mod tests {
assert!(bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64); assert!(bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64);
assert!(entries[..] assert!(entries[..]
.verify_and_hash_transactions(false, false, false) .verify_and_hash_transactions(false, false, false)
.is_some()); .is_ok());
} }
// Big transaction. // Big transaction.
{ {
let tx = make_transaction(25); let tx = make_transaction(25);
let entries = vec![next_entry(&recent_blockhash, 1, vec![tx.clone()])]; let entries = vec![next_entry(&recent_blockhash, 1, vec![tx.clone()])];
assert!(bincode::serialized_size(&tx).unwrap() > PACKET_DATA_SIZE as u64); assert!(bincode::serialized_size(&tx).unwrap() > PACKET_DATA_SIZE as u64);
assert!(entries[..] assert_eq!(
.verify_and_hash_transactions(false, false, false) entries[..]
.is_none()); .verify_and_hash_transactions(false, false, false)
.err(),
Some(TransactionError::SanitizeFailure)
);
} }
// Assert that verify fails as soon as serialized // Assert that verify fails as soon as serialized
// size exceeds packet data size. // size exceeds packet data size.
@ -986,7 +1030,7 @@ mod tests {
bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64, bincode::serialized_size(&tx).unwrap() <= PACKET_DATA_SIZE as u64,
entries[..] entries[..]
.verify_and_hash_transactions(false, false, false) .verify_and_hash_transactions(false, false, false)
.is_some(), .is_ok(),
); );
} }
} }

View File

@ -43,6 +43,7 @@ use solana_sdk::{
native_token::{lamports_to_sol, sol_to_lamports, Sol}, native_token::{lamports_to_sol, sol_to_lamports, Sol},
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
sanitized_transaction::SanitizedTransaction,
shred_version::compute_shred_version, shred_version::compute_shred_version,
stake::{self, state::StakeState}, stake::{self, state::StakeState},
system_program, system_program,
@ -53,6 +54,7 @@ use solana_vote_program::{
vote_state::{self, VoteState}, vote_state::{self, VoteState},
}; };
use std::{ use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap, HashSet}, collections::{BTreeMap, BTreeSet, HashMap, HashSet},
ffi::OsStr, ffi::OsStr,
fs::{self, File}, fs::{self, File},
@ -751,16 +753,19 @@ fn compute_slot_cost(blockstore: &Blockstore, slot: Slot) -> Result<(), String>
let mut cost_model = cost_model.write().unwrap(); let mut cost_model = cost_model.write().unwrap();
for transaction in &entry.transactions { for transaction in &entry.transactions {
programs += transaction.message().instructions.len(); programs += transaction.message().instructions.len();
let tx_cost = match cost_model.calculate_cost(transaction) { let transaction =
Err(err) => { match SanitizedTransaction::try_create(Cow::Borrowed(transaction), Hash::default())
warn!( {
"failed to calculate transaction cost, err {:?}, tx {:?}", Ok(tx) => tx,
err, transaction Err(err) => {
); warn!(
continue; "failed to sanitize transaction, err {:?}, tx {:?}",
} err, transaction
Ok(cost) => cost, );
}; continue;
}
};
let tx_cost = cost_model.calculate_cost(&transaction);
if cost_tracker.try_add(tx_cost).is_err() { if cost_tracker.try_add(tx_cost).is_err() {
println!( println!(
"Slot: {}, CostModel rejected transaction {:?}, stats {:?}!", "Slot: {}, CostModel rejected transaction {:?}, stats {:?}!",

View File

@ -45,6 +45,7 @@ use solana_transaction_status::token_balances::{
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
convert::TryFrom,
path::PathBuf, path::PathBuf,
result, result,
sync::Arc, sync::Arc,
@ -126,7 +127,11 @@ fn execute_batch(
timings, timings,
); );
bank_utils::find_and_send_votes(batch.hashed_transactions(), &tx_results, replay_vote_sender); bank_utils::find_and_send_votes(
batch.sanitized_transactions(),
&tx_results,
replay_vote_sender,
);
let TransactionResults { let TransactionResults {
fee_collection_results, fee_collection_results,
@ -216,7 +221,10 @@ pub fn process_entries(
replay_vote_sender: Option<&ReplayVoteSender>, replay_vote_sender: Option<&ReplayVoteSender>,
) -> Result<()> { ) -> Result<()> {
let mut timings = ExecuteTimings::default(); let mut timings = ExecuteTimings::default();
let mut entry_types: Vec<_> = entries.iter().map(EntryType::from).collect(); let mut entry_types: Vec<_> = entries
.iter()
.map(EntryType::try_from)
.collect::<Result<_>>()?;
let result = process_entries_with_callback( let result = process_entries_with_callback(
bank, bank,
&mut entry_types, &mut entry_types,
@ -276,7 +284,7 @@ fn process_entries_with_callback(
loop { loop {
// try to lock the accounts // try to lock the accounts
let batch = bank.prepare_hashed_batch(transactions); let batch = bank.prepare_sanitized_batch(transactions);
let first_lock_err = first_err(batch.lock_results()); let first_lock_err = first_err(batch.lock_results());
// if locking worked // if locking worked
@ -791,18 +799,13 @@ pub fn confirm_slot(
}; };
let check_start = Instant::now(); let check_start = Instant::now();
let check_result = entries.verify_and_hash_transactions( let mut entries = entries.verify_and_hash_transactions(
skip_verification, skip_verification,
bank.libsecp256k1_0_5_upgrade_enabled(), bank.libsecp256k1_0_5_upgrade_enabled(),
bank.verify_tx_signatures_len_enabled(), bank.verify_tx_signatures_len_enabled(),
); )?;
if check_result.is_none() {
warn!("Ledger proof of history failed at slot: {}", slot);
return Err(BlockError::InvalidEntryHash.into());
}
let transaction_duration_us = timing::duration_as_us(&check_start.elapsed()); let transaction_duration_us = timing::duration_as_us(&check_start.elapsed());
let mut entries = check_result.unwrap();
let mut replay_elapsed = Measure::start("replay_elapsed"); let mut replay_elapsed = Measure::start("replay_elapsed");
let mut execute_timings = ExecuteTimings::default(); let mut execute_timings = ExecuteTimings::default();
// Note: This will shuffle entries' transactions in-place. // Note: This will shuffle entries' transactions in-place.
@ -2410,13 +2413,13 @@ pub mod tests {
// Check all accounts are unlocked // Check all accounts are unlocked
let txs1 = &entry_1_to_mint.transactions[..]; let txs1 = &entry_1_to_mint.transactions[..];
let txs2 = &entry_2_to_3_mint_to_1.transactions[..]; let txs2 = &entry_2_to_3_mint_to_1.transactions[..];
let batch1 = bank.prepare_batch(txs1.iter()); let batch1 = bank.prepare_batch(txs1.iter()).unwrap();
for result in batch1.lock_results() { for result in batch1.lock_results() {
assert!(result.is_ok()); assert!(result.is_ok());
} }
// txs1 and txs2 have accounts that conflict, so we must drop txs1 first // txs1 and txs2 have accounts that conflict, so we must drop txs1 first
drop(batch1); drop(batch1);
let batch2 = bank.prepare_batch(txs2.iter()); let batch2 = bank.prepare_batch(txs2.iter()).unwrap();
for result in batch2.lock_results() { for result in batch2.lock_results() {
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -3173,15 +3176,14 @@ pub mod tests {
bank.last_blockhash(), bank.last_blockhash(),
); );
let account_not_found_sig = account_not_found_tx.signatures[0]; let account_not_found_sig = account_not_found_tx.signatures[0];
let mut account_loaded_twice = system_transaction::transfer( let invalid_blockhash_tx = system_transaction::transfer(
&mint_keypair, &mint_keypair,
&solana_sdk::pubkey::new_rand(), &solana_sdk::pubkey::new_rand(),
42, 42,
bank.last_blockhash(), Hash::default(),
); );
account_loaded_twice.message.account_keys[1] = mint_keypair.pubkey(); let transactions = [account_not_found_tx, invalid_blockhash_tx];
let transactions = [account_not_found_tx, account_loaded_twice]; let batch = bank.prepare_batch(transactions.iter()).unwrap();
let batch = bank.prepare_batch(transactions.iter());
let ( let (
TransactionResults { TransactionResults {
fee_collection_results, fee_collection_results,
@ -3199,7 +3201,6 @@ pub mod tests {
&mut ExecuteTimings::default(), &mut ExecuteTimings::default(),
); );
let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap(); let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap();
// First error found should be for the 2nd transaction, due to iteration_order
assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound); assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound);
assert_eq!(signature, account_not_found_sig); assert_eq!(signature, account_not_found_sig);
} }

View File

@ -293,7 +293,7 @@ fn process_transaction_and_record_inner(
) -> (Result<(), TransactionError>, Vec<Vec<CompiledInstruction>>) { ) -> (Result<(), TransactionError>, Vec<Vec<CompiledInstruction>>) {
let signature = tx.signatures.get(0).unwrap().clone(); let signature = tx.signatures.get(0).unwrap().clone();
let txs = vec![tx]; let txs = vec![tx];
let tx_batch = bank.prepare_batch(txs.iter()); let tx_batch = bank.prepare_batch(txs.iter()).unwrap();
let (mut results, _, mut inner_instructions, _transaction_logs) = bank let (mut results, _, mut inner_instructions, _transaction_logs) = bank
.load_execute_and_commit_transactions( .load_execute_and_commit_transactions(
&tx_batch, &tx_batch,
@ -316,7 +316,7 @@ fn process_transaction_and_record_inner(
} }
fn execute_transactions(bank: &Bank, txs: &[Transaction]) -> Vec<ConfirmedTransaction> { fn execute_transactions(bank: &Bank, txs: &[Transaction]) -> Vec<ConfirmedTransaction> {
let batch = bank.prepare_batch(txs.iter()); let batch = bank.prepare_batch(txs.iter()).unwrap();
let mut timings = ExecuteTimings::default(); let mut timings = ExecuteTimings::default();
let mut mint_decimals = HashMap::new(); let mut mint_decimals = HashMap::new();
let tx_pre_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals); let tx_pre_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals);

View File

@ -41,14 +41,6 @@ fn deposit_many(bank: &Bank, pubkeys: &mut Vec<Pubkey>, num: usize) -> Result<()
Ok(()) Ok(())
} }
#[bench]
fn bench_has_duplicates(bencher: &mut Bencher) {
bencher.iter(|| {
let data = test::black_box([1, 2, 3]);
assert!(!Accounts::has_duplicates(&data));
})
}
#[bench] #[bench]
fn test_accounts_create(bencher: &mut Bencher) { fn test_accounts_create(bencher: &mut Bencher) {
let (genesis_config, _) = create_genesis_config(10_000); let (genesis_config, _) = create_genesis_config(10_000);

View File

@ -84,7 +84,7 @@ pub fn create_native_loader_transactions(
} }
fn sync_bencher(bank: &Arc<Bank>, _bank_client: &BankClient, transactions: &[Transaction]) { fn sync_bencher(bank: &Arc<Bank>, _bank_client: &BankClient, transactions: &[Transaction]) {
let results = bank.process_transactions(transactions); let results = bank.process_transactions(transactions.iter());
assert!(results.iter().all(Result::is_ok)); assert!(results.iter().all(Result::is_ok));
} }
@ -136,7 +136,7 @@ fn do_bench_transactions(
let transactions = create_transactions(&bank_client, &mint_keypair); let transactions = create_transactions(&bank_client, &mint_keypair);
// Do once to fund accounts, load modules, etc... // Do once to fund accounts, load modules, etc...
let results = bank.process_transactions(&transactions); let results = bank.process_transactions(transactions.iter());
assert!(results.iter().all(Result::is_ok)); assert!(results.iter().all(Result::is_ok));
bencher.iter(|| { bencher.iter(|| {

View File

@ -165,19 +165,6 @@ impl Accounts {
} }
} }
/// Return true if the slice has any duplicate elements
pub fn has_duplicates<T: PartialEq>(xs: &[T]) -> bool {
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
// `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
// ~50 times faster than using HashSet for very short slices.
for i in 1..xs.len() {
if xs[i..].contains(&xs[i - 1]) {
return true;
}
}
false
}
fn construct_instructions_account(message: &Message) -> AccountSharedData { fn construct_instructions_account(message: &Message) -> AccountSharedData {
let mut data = message.serialize_instructions(); let mut data = message.serialize_instructions();
// add room for current instruction index. // add room for current instruction index.
@ -858,25 +845,13 @@ impl Accounts {
#[must_use] #[must_use]
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
pub fn lock_accounts<'a>(&self, txs: impl Iterator<Item = &'a Transaction>) -> Vec<Result<()>> { pub fn lock_accounts<'a>(&self, txs: impl Iterator<Item = &'a Transaction>) -> Vec<Result<()>> {
use solana_sdk::sanitize::Sanitize; let keys: Vec<_> = txs
let keys: Vec<Result<_>> = txs .map(|tx| tx.message().get_account_keys_by_lock_type())
.map(|tx| {
tx.sanitize().map_err(TransactionError::from)?;
if Self::has_duplicates(&tx.message.account_keys) {
return Err(TransactionError::AccountLoadedTwice);
}
Ok(tx.message().get_account_keys_by_lock_type())
})
.collect(); .collect();
let mut account_locks = &mut self.account_locks.lock().unwrap(); let mut account_locks = &mut self.account_locks.lock().unwrap();
keys.into_iter() keys.into_iter()
.map(|result| match result { .map(|(writable_keys, readonly_keys)| {
Ok((writable_keys, readonly_keys)) => { self.lock_account(&mut account_locks, writable_keys, readonly_keys)
self.lock_account(&mut account_locks, writable_keys, readonly_keys)
}
Err(e) => Err(e),
}) })
.collect() .collect()
} }
@ -2035,12 +2010,6 @@ mod tests {
); );
} }
#[test]
fn test_has_duplicates() {
assert!(!Accounts::has_duplicates(&[1, 2]));
assert!(Accounts::has_duplicates(&[1, 2, 1]));
}
#[test] #[test]
fn huge_clean() { fn huge_clean() {
solana_logger::setup(); solana_logger::setup();

View File

@ -84,7 +84,6 @@ use solana_sdk::{
genesis_config::{ClusterType, GenesisConfig}, genesis_config::{ClusterType, GenesisConfig},
hard_forks::HardForks, hard_forks::HardForks,
hash::{extend_and_hash, hashv, Hash}, hash::{extend_and_hash, hashv, Hash},
hashed_transaction::{HashedTransaction, HashedTransactionSlice},
incinerator, incinerator,
inflation::Inflation, inflation::Inflation,
instruction::CompiledInstruction, instruction::CompiledInstruction,
@ -97,7 +96,7 @@ use solana_sdk::{
program_utils::limited_deserialize, program_utils::limited_deserialize,
pubkey::Pubkey, pubkey::Pubkey,
recent_blockhashes_account, recent_blockhashes_account,
sanitize::Sanitize, sanitized_transaction::{SanitizedTransaction, SanitizedTransactionSlice},
signature::{Keypair, Signature}, signature::{Keypair, Signature},
slot_hashes::SlotHashes, slot_hashes::SlotHashes,
slot_history::SlotHistory, slot_history::SlotHistory,
@ -2588,19 +2587,18 @@ impl Bank {
fn update_transaction_statuses( fn update_transaction_statuses(
&self, &self,
hashed_txs: &[HashedTransaction], sanitized_txs: &[SanitizedTransaction],
res: &[TransactionExecutionResult], res: &[TransactionExecutionResult],
) { ) {
let mut status_cache = self.src.status_cache.write().unwrap(); let mut status_cache = self.src.status_cache.write().unwrap();
assert_eq!(hashed_txs.len(), res.len()); assert_eq!(sanitized_txs.len(), res.len());
for (hashed_tx, (res, _nonce_rollback)) in hashed_txs.iter().zip(res) { for (tx, (res, _nonce_rollback)) in sanitized_txs.iter().zip(res) {
let tx = hashed_tx.transaction();
if Self::can_commit(res) && !tx.signatures.is_empty() { if Self::can_commit(res) && !tx.signatures.is_empty() {
// Add the message hash to the status cache to ensure that this message // Add the message hash to the status cache to ensure that this message
// won't be processed again with a different signature. // won't be processed again with a different signature.
status_cache.insert( status_cache.insert(
&tx.message().recent_blockhash, &tx.message().recent_blockhash,
&hashed_tx.message_hash, &tx.message_hash,
self.slot(), self.slot(),
res.clone(), res.clone(),
); );
@ -2654,44 +2652,37 @@ impl Bank {
pub fn prepare_batch<'a, 'b>( pub fn prepare_batch<'a, 'b>(
&'a self, &'a self,
txs: impl Iterator<Item = &'b Transaction>, txs: impl Iterator<Item = &'b Transaction>,
) -> TransactionBatch<'a, 'b> { ) -> Result<TransactionBatch<'a, 'b>> {
let hashed_txs: Vec<HashedTransaction> = txs.map(HashedTransaction::from).collect(); let sanitized_txs: Vec<SanitizedTransaction> = txs
.map(SanitizedTransaction::try_from)
.collect::<Result<_>>()?;
let lock_results = self let lock_results = self
.rc .rc
.accounts .accounts
.lock_accounts(hashed_txs.as_transactions_iter()); .lock_accounts(sanitized_txs.as_transactions_iter());
TransactionBatch::new(lock_results, self, Cow::Owned(hashed_txs)) Ok(TransactionBatch::new(
lock_results,
self,
Cow::Owned(sanitized_txs),
))
} }
pub fn prepare_hashed_batch<'a, 'b>( pub fn prepare_sanitized_batch<'a, 'b>(
&'a self, &'a self,
hashed_txs: &'b [HashedTransaction], sanitized_txs: &'b [SanitizedTransaction],
) -> TransactionBatch<'a, 'b> { ) -> TransactionBatch<'a, 'b> {
let lock_results = self let lock_results = self
.rc .rc
.accounts .accounts
.lock_accounts(hashed_txs.as_transactions_iter()); .lock_accounts(sanitized_txs.as_transactions_iter());
TransactionBatch::new(lock_results, self, Cow::Borrowed(hashed_txs)) TransactionBatch::new(lock_results, self, Cow::Borrowed(sanitized_txs))
} }
pub(crate) fn prepare_simulation_batch<'a, 'b>( pub(crate) fn prepare_simulation_batch<'a, 'b>(
&'a self, &'a self,
tx: &'b Transaction, tx: SanitizedTransaction<'b>,
) -> TransactionBatch<'a, 'b> { ) -> TransactionBatch<'a, 'b> {
let check_transaction = |tx: &Transaction| -> Result<()> { let mut batch = TransactionBatch::new(vec![Ok(())], self, Cow::Owned(vec![tx]));
tx.sanitize().map_err(TransactionError::from)?;
if Accounts::has_duplicates(&tx.message.account_keys) {
Err(TransactionError::AccountLoadedTwice)
} else {
Ok(())
}
};
let mut batch = TransactionBatch::new(
vec![check_transaction(tx)],
self,
Cow::Owned(vec![HashedTransaction::from(tx)]),
);
batch.needs_unlock = false; batch.needs_unlock = false;
batch batch
} }
@ -2707,7 +2698,10 @@ impl Bank {
) { ) {
assert!(self.is_frozen(), "simulation bank must be frozen"); assert!(self.is_frozen(), "simulation bank must be frozen");
let batch = self.prepare_simulation_batch(transaction); let batch = match SanitizedTransaction::try_from(transaction) {
Ok(sanitized_tx) => self.prepare_simulation_batch(sanitized_tx),
Err(err) => return (Err(err), vec![], vec![]),
};
let mut timings = ExecuteTimings::default(); let mut timings = ExecuteTimings::default();
@ -2795,11 +2789,11 @@ impl Bank {
fn is_tx_already_processed( fn is_tx_already_processed(
&self, &self,
hashed_tx: &HashedTransaction, sanitized_tx: &SanitizedTransaction,
status_cache: &StatusCache<Result<()>>, status_cache: &StatusCache<Result<()>>,
) -> bool { ) -> bool {
let key = &hashed_tx.message_hash; let key = &sanitized_tx.message_hash;
let transaction_blockhash = &hashed_tx.transaction().message().recent_blockhash; let transaction_blockhash = &sanitized_tx.message().recent_blockhash;
status_cache status_cache
.get_status(key, transaction_blockhash, &self.ancestors) .get_status(key, transaction_blockhash, &self.ancestors)
.is_some() .is_some()
@ -2807,16 +2801,16 @@ impl Bank {
fn check_status_cache( fn check_status_cache(
&self, &self,
hashed_txs: &[HashedTransaction], sanitized_txs: &[SanitizedTransaction],
lock_results: Vec<TransactionCheckResult>, lock_results: Vec<TransactionCheckResult>,
error_counters: &mut ErrorCounters, error_counters: &mut ErrorCounters,
) -> Vec<TransactionCheckResult> { ) -> Vec<TransactionCheckResult> {
let rcache = self.src.status_cache.read().unwrap(); let rcache = self.src.status_cache.read().unwrap();
hashed_txs sanitized_txs
.iter() .iter()
.zip(lock_results) .zip(lock_results)
.map(|(hashed_tx, (lock_res, nonce_rollback))| { .map(|(sanitized_tx, (lock_res, nonce_rollback))| {
if lock_res.is_ok() && self.is_tx_already_processed(hashed_tx, &rcache) { if lock_res.is_ok() && self.is_tx_already_processed(sanitized_tx, &rcache) {
error_counters.already_processed += 1; error_counters.already_processed += 1;
return (Err(TransactionError::AlreadyProcessed), None); return (Err(TransactionError::AlreadyProcessed), None);
} }
@ -2881,22 +2875,23 @@ impl Bank {
pub fn check_transactions( pub fn check_transactions(
&self, &self,
hashed_txs: &[HashedTransaction], sanitized_txs: &[SanitizedTransaction],
lock_results: &[Result<()>], lock_results: &[Result<()>],
max_age: usize, max_age: usize,
mut error_counters: &mut ErrorCounters, mut error_counters: &mut ErrorCounters,
) -> Vec<TransactionCheckResult> { ) -> Vec<TransactionCheckResult> {
let age_results = self.check_age( let age_results = self.check_age(
hashed_txs.as_transactions_iter(), sanitized_txs.as_transactions_iter(),
lock_results.to_vec(), lock_results.to_vec(),
max_age, max_age,
&mut error_counters, &mut error_counters,
); );
let cache_results = self.check_status_cache(hashed_txs, age_results, &mut error_counters); let cache_results =
self.check_status_cache(sanitized_txs, age_results, &mut error_counters);
if self.upgrade_epoch() { if self.upgrade_epoch() {
// Reject all non-vote transactions // Reject all non-vote transactions
self.filter_by_vote_transactions( self.filter_by_vote_transactions(
hashed_txs.as_transactions_iter(), sanitized_txs.as_transactions_iter(),
cache_results, cache_results,
&mut error_counters, &mut error_counters,
) )
@ -3131,9 +3126,9 @@ impl Bank {
u64, u64,
u64, u64,
) { ) {
let hashed_txs = batch.hashed_transactions(); let sanitized_txs = batch.sanitized_transactions();
debug!("processing transactions: {}", hashed_txs.len()); debug!("processing transactions: {}", sanitized_txs.len());
inc_new_counter_info!("bank-process_transactions", hashed_txs.len()); inc_new_counter_info!("bank-process_transactions", sanitized_txs.len());
let mut error_counters = ErrorCounters::default(); let mut error_counters = ErrorCounters::default();
let retryable_txs: Vec<_> = batch let retryable_txs: Vec<_> = batch
@ -3152,7 +3147,7 @@ impl Bank {
let mut check_time = Measure::start("check_transactions"); let mut check_time = Measure::start("check_transactions");
let check_results = self.check_transactions( let check_results = self.check_transactions(
hashed_txs, sanitized_txs,
batch.lock_results(), batch.lock_results(),
max_age, max_age,
&mut error_counters, &mut error_counters,
@ -3162,7 +3157,7 @@ impl Bank {
let mut load_time = Measure::start("accounts_load"); let mut load_time = Measure::start("accounts_load");
let mut loaded_txs = self.rc.accounts.load_accounts( let mut loaded_txs = self.rc.accounts.load_accounts(
&self.ancestors, &self.ancestors,
hashed_txs.as_transactions_iter(), sanitized_txs.as_transactions_iter(),
check_results, check_results,
&self.blockhash_queue.read().unwrap(), &self.blockhash_queue.read().unwrap(),
&mut error_counters, &mut error_counters,
@ -3174,16 +3169,16 @@ impl Bank {
let mut execution_time = Measure::start("execution_time"); let mut execution_time = Measure::start("execution_time");
let mut signature_count: u64 = 0; let mut signature_count: u64 = 0;
let mut inner_instructions: Vec<Option<InnerInstructionsList>> = let mut inner_instructions: Vec<Option<InnerInstructionsList>> =
Vec::with_capacity(hashed_txs.len()); Vec::with_capacity(sanitized_txs.len());
let mut transaction_log_messages: Vec<Option<Vec<String>>> = let mut transaction_log_messages: Vec<Option<Vec<String>>> =
Vec::with_capacity(hashed_txs.len()); Vec::with_capacity(sanitized_txs.len());
let bpf_compute_budget = self let bpf_compute_budget = self
.bpf_compute_budget .bpf_compute_budget
.unwrap_or_else(BpfComputeBudget::new); .unwrap_or_else(BpfComputeBudget::new);
let executed: Vec<TransactionExecutionResult> = loaded_txs let executed: Vec<TransactionExecutionResult> = loaded_txs
.iter_mut() .iter_mut()
.zip(hashed_txs.as_transactions_iter()) .zip(sanitized_txs.as_transactions_iter())
.map(|(accs, tx)| match accs { .map(|(accs, tx)| match accs {
(Err(e), _nonce_rollback) => { (Err(e), _nonce_rollback) => {
inner_instructions.push(None); inner_instructions.push(None);
@ -3270,7 +3265,7 @@ impl Bank {
check_time.as_us(), check_time.as_us(),
load_time.as_us(), load_time.as_us(),
execution_time.as_us(), execution_time.as_us(),
hashed_txs.len(), sanitized_txs.len(),
); );
timings.check_us += check_time.as_us(); timings.check_us += check_time.as_us();
timings.load_us += load_time.as_us(); timings.load_us += load_time.as_us();
@ -3281,8 +3276,7 @@ impl Bank {
let transaction_log_collector_config = let transaction_log_collector_config =
self.transaction_log_collector_config.read().unwrap(); self.transaction_log_collector_config.read().unwrap();
for (i, ((r, _nonce_rollback), hashed_tx)) in executed.iter().zip(hashed_txs).enumerate() { for (i, ((r, _nonce_rollback), tx)) in executed.iter().zip(sanitized_txs).enumerate() {
let tx = hashed_tx.transaction();
if let Some(debug_keys) = &self.transaction_debug_keys { if let Some(debug_keys) = &self.transaction_debug_keys {
for key in &tx.message.account_keys { for key in &tx.message.account_keys {
if debug_keys.contains(key) { if debug_keys.contains(key) {
@ -3423,7 +3417,7 @@ impl Bank {
pub fn commit_transactions( pub fn commit_transactions(
&self, &self,
hashed_txs: &[HashedTransaction], sanitized_txs: &[SanitizedTransaction],
loaded_txs: &mut [TransactionLoadResult], loaded_txs: &mut [TransactionLoadResult],
executed: &[TransactionExecutionResult], executed: &[TransactionExecutionResult],
tx_count: u64, tx_count: u64,
@ -3441,8 +3435,8 @@ impl Bank {
inc_new_counter_info!("bank-process_transactions-txs", tx_count as usize); inc_new_counter_info!("bank-process_transactions-txs", tx_count as usize);
inc_new_counter_info!("bank-process_transactions-sigs", signature_count as usize); inc_new_counter_info!("bank-process_transactions-sigs", signature_count as usize);
if !hashed_txs.is_empty() { if !sanitized_txs.is_empty() {
let processed_tx_count = hashed_txs.len() as u64; let processed_tx_count = sanitized_txs.len() as u64;
let failed_tx_count = processed_tx_count.saturating_sub(tx_count); let failed_tx_count = processed_tx_count.saturating_sub(tx_count);
self.transaction_error_count self.transaction_error_count
.fetch_add(failed_tx_count, Relaxed); .fetch_add(failed_tx_count, Relaxed);
@ -3461,7 +3455,7 @@ impl Bank {
let mut write_time = Measure::start("write_time"); let mut write_time = Measure::start("write_time");
self.rc.accounts.store_cached( self.rc.accounts.store_cached(
self.slot(), self.slot(),
hashed_txs.as_transactions_iter(), sanitized_txs.as_transactions_iter(),
executed, executed,
loaded_txs, loaded_txs,
&self.rent_collector, &self.rent_collector,
@ -3472,19 +3466,19 @@ impl Bank {
let rent_debits = self.collect_rent(executed, loaded_txs); let rent_debits = self.collect_rent(executed, loaded_txs);
let overwritten_vote_accounts = let overwritten_vote_accounts =
self.update_cached_accounts(hashed_txs.as_transactions_iter(), executed, loaded_txs); self.update_cached_accounts(sanitized_txs.as_transactions_iter(), executed, loaded_txs);
// once committed there is no way to unroll // once committed there is no way to unroll
write_time.stop(); write_time.stop();
debug!( debug!(
"store: {}us txs_len={}", "store: {}us txs_len={}",
write_time.as_us(), write_time.as_us(),
hashed_txs.len() sanitized_txs.len()
); );
timings.store_us += write_time.as_us(); timings.store_us += write_time.as_us();
self.update_transaction_statuses(hashed_txs, executed); self.update_transaction_statuses(sanitized_txs, executed);
let fee_collection_results = let fee_collection_results = self
self.filter_program_errors_and_collect_fee(hashed_txs.as_transactions_iter(), executed); .filter_program_errors_and_collect_fee(sanitized_txs.as_transactions_iter(), executed);
TransactionResults { TransactionResults {
fee_collection_results, fee_collection_results,
@ -4115,7 +4109,7 @@ impl Bank {
); );
let results = self.commit_transactions( let results = self.commit_transactions(
batch.hashed_transactions(), batch.sanitized_transactions(),
&mut loaded_txs, &mut loaded_txs,
&executed, &executed,
tx_count, tx_count,
@ -4136,19 +4130,35 @@ impl Bank {
} }
/// Process a Transaction. This is used for unit tests and simply calls the vector /// Process a Transaction. This is used for unit tests and simply calls the vector
/// Bank::process_transactions method /// Bank::process_transactions method.
pub fn process_transaction(&self, tx: &Transaction) -> Result<()> { pub fn process_transaction(&self, tx: &Transaction) -> Result<()> {
let batch = self.prepare_batch(std::iter::once(tx)); self.try_process_transactions(std::iter::once(tx))?[0].clone()?;
self.process_transaction_batch(&batch)[0].clone()?;
tx.signatures tx.signatures
.get(0) .get(0)
.map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap()) .map_or(Ok(()), |sig| self.get_signature_status(sig).unwrap())
} }
/// Process multiple transaction in a single batch. This is used for benches and unit tests.
///
/// # Panics
///
/// Panics if any of the transactions do not pass sanitization checks.
#[must_use] #[must_use]
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> { pub fn process_transactions<'a>(
let batch = self.prepare_batch(txs.iter()); &self,
self.process_transaction_batch(&batch) txs: impl Iterator<Item = &'a Transaction>,
) -> Vec<Result<()>> {
self.try_process_transactions(txs).unwrap()
}
/// Process multiple transaction in a single batch. This is used for benches and unit tests.
/// Short circuits if any of the transactions do not pass sanitization checks.
pub fn try_process_transactions<'a>(
&self,
txs: impl Iterator<Item = &'a Transaction>,
) -> Result<Vec<Result<()>>> {
let batch = self.prepare_batch(txs)?;
Ok(self.process_transaction_batch(&batch))
} }
#[must_use] #[must_use]
@ -5813,7 +5823,8 @@ pub(crate) mod tests {
let t3 = let t3 =
system_transaction::transfer(&keypair5, &keypair6.pubkey(), 1, genesis_config.hash()); system_transaction::transfer(&keypair5, &keypair6.pubkey(), 1, genesis_config.hash());
let res = bank.process_transactions(&[t1.clone(), t2.clone(), t3]); let txs = vec![t1.clone(), t2.clone(), t3];
let res = bank.process_transactions(txs.iter());
assert_eq!(res.len(), 3); assert_eq!(res.len(), 3);
assert_eq!(res[0], Ok(())); assert_eq!(res[0], Ok(()));
@ -5825,7 +5836,8 @@ pub(crate) mod tests {
let rwlockguard_bank_hash = bank.hash.read().unwrap(); let rwlockguard_bank_hash = bank.hash.read().unwrap();
let bank_hash = rwlockguard_bank_hash.as_ref(); let bank_hash = rwlockguard_bank_hash.as_ref();
let res = bank_with_success_txs.process_transactions(&[t2, t1]); let txs = vec![t2, t1];
let res = bank_with_success_txs.process_transactions(txs.iter());
assert_eq!(res.len(), 2); assert_eq!(res.len(), 2);
assert_eq!(res[0], Ok(())); assert_eq!(res[0], Ok(()));
@ -6528,7 +6540,8 @@ pub(crate) mod tests {
genesis_config.hash(), genesis_config.hash(),
); );
let res = bank.process_transactions(&[t6, t5, t1, t2, t3, t4]); let txs = vec![t6, t5, t1, t2, t3, t4];
let res = bank.process_transactions(txs.iter());
assert_eq!(res.len(), 6); assert_eq!(res.len(), 6);
assert_eq!(res[0], Ok(())); assert_eq!(res[0], Ok(()));
@ -7717,7 +7730,8 @@ pub(crate) mod tests {
let t1 = system_transaction::transfer(&mint_keypair, &key1, 1, genesis_config.hash()); let t1 = system_transaction::transfer(&mint_keypair, &key1, 1, genesis_config.hash());
let t2 = system_transaction::transfer(&mint_keypair, &key2, 1, genesis_config.hash()); let t2 = system_transaction::transfer(&mint_keypair, &key2, 1, genesis_config.hash());
let res = bank.process_transactions(&[t1.clone(), t2.clone()]); let txs = vec![t1.clone(), t2.clone()];
let res = bank.process_transactions(txs.iter());
assert_eq!(res.len(), 2); assert_eq!(res.len(), 2);
assert_eq!(res[0], Ok(())); assert_eq!(res[0], Ok(()));
@ -8164,7 +8178,7 @@ pub(crate) mod tests {
genesis_config.hash(), genesis_config.hash(),
); );
let txs = vec![tx0, tx1]; let txs = vec![tx0, tx1];
let results = bank.process_transactions(&txs); let results = bank.process_transactions(txs.iter());
assert!(results[1].is_err()); assert!(results[1].is_err());
// Assert bad transactions aren't counted. // Assert bad transactions aren't counted.
@ -8220,7 +8234,7 @@ pub(crate) mod tests {
bank.last_blockhash(), bank.last_blockhash(),
); );
let txs = vec![tx0, tx1]; let txs = vec![tx0, tx1];
let results = bank.process_transactions(&txs); let results = bank.process_transactions(txs.iter());
// If multiple transactions attempt to read the same account, they should succeed. // If multiple transactions attempt to read the same account, they should succeed.
// Vote authorized_voter and sysvar accounts are given read-only handling // Vote authorized_voter and sysvar accounts are given read-only handling
@ -8241,7 +8255,7 @@ pub(crate) mod tests {
bank.last_blockhash(), bank.last_blockhash(),
); );
let txs = vec![tx0, tx1]; let txs = vec![tx0, tx1];
let results = bank.process_transactions(&txs); let results = bank.process_transactions(txs.iter());
// However, an account may not be locked as read-only and writable at the same time. // However, an account may not be locked as read-only and writable at the same time.
assert_eq!(results[0], Ok(())); assert_eq!(results[0], Ok(()));
assert_eq!(results[1], Err(TransactionError::AccountInUse)); assert_eq!(results[1], Err(TransactionError::AccountInUse));
@ -8258,7 +8272,7 @@ pub(crate) mod tests {
system_transaction::transfer(&mint_keypair, &alice.pubkey(), 1, genesis_config.hash()); system_transaction::transfer(&mint_keypair, &alice.pubkey(), 1, genesis_config.hash());
let pay_alice = vec![tx1]; let pay_alice = vec![tx1];
let lock_result = bank.prepare_batch(pay_alice.iter()); let lock_result = bank.prepare_batch(pay_alice.iter()).unwrap();
let results_alice = bank let results_alice = bank
.load_execute_and_commit_transactions( .load_execute_and_commit_transactions(
&lock_result, &lock_result,
@ -8311,7 +8325,7 @@ pub(crate) mod tests {
let tx = Transaction::new(&[&key0], message, genesis_config.hash()); let tx = Transaction::new(&[&key0], message, genesis_config.hash());
let txs = vec![tx]; let txs = vec![tx];
let batch0 = bank.prepare_batch(txs.iter()); let batch0 = bank.prepare_batch(txs.iter()).unwrap();
assert!(batch0.lock_results()[0].is_ok()); assert!(batch0.lock_results()[0].is_ok());
// Try locking accounts, locking a previously read-only account as writable // Try locking accounts, locking a previously read-only account as writable
@ -8329,7 +8343,7 @@ pub(crate) mod tests {
let tx = Transaction::new(&[&key1], message, genesis_config.hash()); let tx = Transaction::new(&[&key1], message, genesis_config.hash());
let txs = vec![tx]; let txs = vec![tx];
let batch1 = bank.prepare_batch(txs.iter()); let batch1 = bank.prepare_batch(txs.iter()).unwrap();
assert!(batch1.lock_results()[0].is_err()); assert!(batch1.lock_results()[0].is_err());
// Try locking a previously read-only account a 2nd time; should succeed // Try locking a previously read-only account a 2nd time; should succeed
@ -8346,7 +8360,7 @@ pub(crate) mod tests {
let tx = Transaction::new(&[&key2], message, genesis_config.hash()); let tx = Transaction::new(&[&key2], message, genesis_config.hash());
let txs = vec![tx]; let txs = vec![tx];
let batch2 = bank.prepare_batch(txs.iter()); let batch2 = bank.prepare_batch(txs.iter()).unwrap();
assert!(batch2.lock_results()[0].is_ok()); assert!(batch2.lock_results()[0].is_ok());
} }
@ -10231,14 +10245,14 @@ pub(crate) mod tests {
instructions, instructions,
); );
let txs = vec![tx0, tx1]; let txs = vec![tx0, tx1];
let batch = bank0.prepare_batch(txs.iter()); let batch = bank0.prepare_batch(txs.iter()).unwrap();
let balances = bank0.collect_balances(&batch); let balances = bank0.collect_balances(&batch);
assert_eq!(balances.len(), 2); assert_eq!(balances.len(), 2);
assert_eq!(balances[0], vec![8, 11, 1]); assert_eq!(balances[0], vec![8, 11, 1]);
assert_eq!(balances[1], vec![8, 0, 1]); assert_eq!(balances[1], vec![8, 0, 1]);
let txs: Vec<_> = txs.iter().rev().cloned().collect(); let txs: Vec<_> = txs.iter().rev().cloned().collect();
let batch = bank0.prepare_batch(txs.iter()); let batch = bank0.prepare_batch(txs.iter()).unwrap();
let balances = bank0.collect_balances(&batch); let balances = bank0.collect_balances(&batch);
assert_eq!(balances.len(), 2); assert_eq!(balances.len(), 2);
assert_eq!(balances[0], vec![8, 0, 1]); assert_eq!(balances[0], vec![8, 0, 1]);
@ -10272,7 +10286,7 @@ pub(crate) mod tests {
let tx2 = system_transaction::transfer(&keypair1, &pubkey2, 12, blockhash); let tx2 = system_transaction::transfer(&keypair1, &pubkey2, 12, blockhash);
let txs = vec![tx0, tx1, tx2]; let txs = vec![tx0, tx1, tx2];
let lock_result = bank0.prepare_batch(txs.iter()); let lock_result = bank0.prepare_batch(txs.iter()).unwrap();
let (transaction_results, transaction_balances_set, inner_instructions, transaction_logs) = let (transaction_results, transaction_balances_set, inner_instructions, transaction_logs) =
bank0.load_execute_and_commit_transactions( bank0.load_execute_and_commit_transactions(
&lock_result, &lock_result,
@ -13335,10 +13349,9 @@ pub(crate) mod tests {
let success_sig = tx0.signatures[0]; let success_sig = tx0.signatures[0];
let tx1 = system_transaction::transfer(&sender1, &recipient1, 110, blockhash); // Should produce insufficient funds log let tx1 = system_transaction::transfer(&sender1, &recipient1, 110, blockhash); // Should produce insufficient funds log
let failure_sig = tx1.signatures[0]; let failure_sig = tx1.signatures[0];
let mut invalid_tx = system_transaction::transfer(&sender1, &recipient1, 10, blockhash); let tx2 = system_transaction::transfer(&sender0, &recipient0, 1, blockhash);
invalid_tx.message.header.num_required_signatures = 0; // this tx won't be processed because it has no signers let txs = vec![tx0, tx1, tx2];
let txs = vec![invalid_tx, tx1, tx0]; let batch = bank.prepare_batch(txs.iter()).unwrap();
let batch = bank.prepare_batch(txs.iter());
let log_results = bank let log_results = bank
.load_execute_and_commit_transactions( .load_execute_and_commit_transactions(
@ -13351,9 +13364,9 @@ pub(crate) mod tests {
) )
.3; .3;
assert_eq!(log_results.len(), 3); assert_eq!(log_results.len(), 3);
assert!(log_results[0].as_ref().is_none()); assert!(log_results[0].as_ref().unwrap()[1].contains(&"success".to_string()));
assert!(log_results[1].as_ref().unwrap()[2].contains(&"failed".to_string())); assert!(log_results[1].as_ref().unwrap()[2].contains(&"failed".to_string()));
assert!(log_results[2].as_ref().unwrap()[1].contains(&"success".to_string())); assert!(log_results[2].as_ref().is_none());
let stored_logs = &bank.transaction_log_collector.read().unwrap().logs; let stored_logs = &bank.transaction_log_collector.read().unwrap().logs;
let success_log_info = stored_logs let success_log_info = stored_logs

View File

@ -280,7 +280,7 @@ impl BankClient {
while let Ok(tx) = transaction_receiver.try_recv() { while let Ok(tx) = transaction_receiver.try_recv() {
transactions.push(tx); transactions.push(tx);
} }
let _ = bank.process_transactions(&transactions); let _ = bank.try_process_transactions(transactions.iter());
} }
} }

View File

@ -3,7 +3,7 @@ use crate::{
genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs}, genesis_utils::{self, GenesisConfigInfo, ValidatorVoteKeypairs},
vote_sender_types::ReplayVoteSender, vote_sender_types::ReplayVoteSender,
}; };
use solana_sdk::{hashed_transaction::HashedTransaction, pubkey::Pubkey, signature::Signer}; use solana_sdk::{pubkey::Pubkey, sanitized_transaction::SanitizedTransaction, signature::Signer};
use solana_vote_program::vote_transaction; use solana_vote_program::vote_transaction;
pub fn setup_bank_and_vote_pubkeys(num_vote_accounts: usize, stake: u64) -> (Bank, Vec<Pubkey>) { pub fn setup_bank_and_vote_pubkeys(num_vote_accounts: usize, stake: u64) -> (Bank, Vec<Pubkey>) {
@ -27,7 +27,7 @@ pub fn setup_bank_and_vote_pubkeys(num_vote_accounts: usize, stake: u64) -> (Ban
} }
pub fn find_and_send_votes( pub fn find_and_send_votes(
hashed_txs: &[HashedTransaction], sanitized_txs: &[SanitizedTransaction],
tx_results: &TransactionResults, tx_results: &TransactionResults,
vote_sender: Option<&ReplayVoteSender>, vote_sender: Option<&ReplayVoteSender>,
) { ) {
@ -41,7 +41,7 @@ pub fn find_and_send_votes(
assert!(execution_results[old_account.transaction_result_index] assert!(execution_results[old_account.transaction_result_index]
.0 .0
.is_ok()); .is_ok());
let transaction = hashed_txs[old_account.transaction_index].transaction(); let transaction = &sanitized_txs[old_account.transaction_index];
if let Some(parsed_vote) = vote_transaction::parse_vote_transaction(transaction) { if let Some(parsed_vote) = vote_transaction::parse_vote_transaction(transaction) {
if parsed_vote.1.slots.last().is_some() { if parsed_vote.1.slots.last().is_some() {
let _ = vote_sender.send(parsed_vote); let _ = vote_sender.send(parsed_vote);

View File

@ -1,13 +1,16 @@
use crate::bank::Bank; use crate::bank::Bank;
use solana_sdk::hashed_transaction::HashedTransaction; use solana_sdk::{
use solana_sdk::transaction::{Result, Transaction}; sanitized_transaction::SanitizedTransaction,
transaction::{Result, Transaction},
};
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::Deref;
// Represents the results of trying to lock a set of accounts // Represents the results of trying to lock a set of accounts
pub struct TransactionBatch<'a, 'b> { pub struct TransactionBatch<'a, 'b> {
lock_results: Vec<Result<()>>, lock_results: Vec<Result<()>>,
bank: &'a Bank, bank: &'a Bank,
hashed_txs: Cow<'b, [HashedTransaction<'b>]>, sanitized_txs: Cow<'b, [SanitizedTransaction<'b>]>,
pub(crate) needs_unlock: bool, pub(crate) needs_unlock: bool,
} }
@ -15,13 +18,13 @@ impl<'a, 'b> TransactionBatch<'a, 'b> {
pub fn new( pub fn new(
lock_results: Vec<Result<()>>, lock_results: Vec<Result<()>>,
bank: &'a Bank, bank: &'a Bank,
hashed_txs: Cow<'b, [HashedTransaction<'b>]>, sanitized_txs: Cow<'b, [SanitizedTransaction<'b>]>,
) -> Self { ) -> Self {
assert_eq!(lock_results.len(), hashed_txs.len()); assert_eq!(lock_results.len(), sanitized_txs.len());
Self { Self {
lock_results, lock_results,
bank, bank,
hashed_txs, sanitized_txs,
needs_unlock: true, needs_unlock: true,
} }
} }
@ -30,12 +33,12 @@ impl<'a, 'b> TransactionBatch<'a, 'b> {
&self.lock_results &self.lock_results
} }
pub fn hashed_transactions(&self) -> &[HashedTransaction] { pub fn sanitized_transactions(&self) -> &[SanitizedTransaction] {
&self.hashed_txs &self.sanitized_txs
} }
pub fn transactions_iter(&self) -> impl Iterator<Item = &Transaction> { pub fn transactions_iter(&self) -> impl Iterator<Item = &Transaction> {
self.hashed_txs.iter().map(|h| h.transaction()) self.sanitized_txs.iter().map(Deref::deref)
} }
pub fn bank(&self) -> &Bank { pub fn bank(&self) -> &Bank {
@ -55,43 +58,49 @@ mod tests {
use super::*; use super::*;
use crate::genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}; use crate::genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo};
use solana_sdk::{signature::Keypair, system_transaction}; use solana_sdk::{signature::Keypair, system_transaction};
use std::convert::TryFrom;
#[test] #[test]
fn test_transaction_batch() { fn test_transaction_batch() {
let (bank, txs) = setup(); let (bank, txs) = setup();
// Test getting locked accounts // Test getting locked accounts
let batch = bank.prepare_batch(txs.iter()); let batch = bank.prepare_batch(txs.iter()).unwrap();
// Grab locks // Grab locks
assert!(batch.lock_results().iter().all(|x| x.is_ok())); assert!(batch.lock_results().iter().all(|x| x.is_ok()));
// Trying to grab locks again should fail // Trying to grab locks again should fail
let batch2 = bank.prepare_batch(txs.iter()); let batch2 = bank.prepare_batch(txs.iter()).unwrap();
assert!(batch2.lock_results().iter().all(|x| x.is_err())); assert!(batch2.lock_results().iter().all(|x| x.is_err()));
// Drop the first set of locks // Drop the first set of locks
drop(batch); drop(batch);
// Now grabbing locks should work again // Now grabbing locks should work again
let batch2 = bank.prepare_batch(txs.iter()); let batch2 = bank.prepare_batch(txs.iter()).unwrap();
assert!(batch2.lock_results().iter().all(|x| x.is_ok())); assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
} }
#[test] #[test]
fn test_simulation_batch() { fn test_simulation_batch() {
let (bank, txs) = setup(); let (bank, txs) = setup();
let txs = txs
.into_iter()
.map(SanitizedTransaction::try_from)
.collect::<Result<Vec<_>>>()
.unwrap();
// Prepare batch without locks // Prepare batch without locks
let batch = bank.prepare_simulation_batch(&txs[0]); let batch = bank.prepare_simulation_batch(txs[0].clone());
assert!(batch.lock_results().iter().all(|x| x.is_ok())); assert!(batch.lock_results().iter().all(|x| x.is_ok()));
// Grab locks // Grab locks
let batch2 = bank.prepare_batch(txs.iter()); let batch2 = bank.prepare_sanitized_batch(&txs);
assert!(batch2.lock_results().iter().all(|x| x.is_ok())); assert!(batch2.lock_results().iter().all(|x| x.is_ok()));
// Prepare another batch without locks // Prepare another batch without locks
let batch3 = bank.prepare_simulation_batch(&txs[0]); let batch3 = bank.prepare_simulation_batch(txs[0].clone());
assert!(batch3.lock_results().iter().all(|x| x.is_ok())); assert!(batch3.lock_results().iter().all(|x| x.is_ok()));
} }

View File

@ -0,0 +1,13 @@
#![feature(test)]
extern crate test;
use solana_sdk::sanitized_transaction::SanitizedTransaction;
use test::Bencher;
#[bench]
fn bench_has_duplicates(bencher: &mut Bencher) {
bencher.iter(|| {
let data = test::black_box([1, 2, 3]);
assert!(!SanitizedTransaction::has_duplicates(&data));
})
}

View File

@ -1,52 +0,0 @@
#![cfg(feature = "full")]
use crate::{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()))
}
}

View File

@ -26,7 +26,6 @@ pub mod feature_set;
pub mod genesis_config; pub mod genesis_config;
pub mod hard_forks; pub mod hard_forks;
pub mod hash; pub mod hash;
pub mod hashed_transaction;
pub mod inflation; pub mod inflation;
pub mod keyed_account; pub mod keyed_account;
pub mod log; pub mod log;
@ -40,6 +39,7 @@ pub mod program_utils;
pub mod pubkey; pub mod pubkey;
pub mod recent_blockhashes_account; pub mod recent_blockhashes_account;
pub mod rpc_port; pub mod rpc_port;
pub mod sanitized_transaction;
pub mod secp256k1_instruction; pub mod secp256k1_instruction;
pub mod shred_version; pub mod shred_version;
pub mod signature; pub mod signature;

View File

@ -0,0 +1,87 @@
#![cfg(feature = "full")]
use crate::{
hash::Hash,
sanitize::Sanitize,
transaction::{Result, Transaction, TransactionError},
};
use std::{borrow::Cow, convert::TryFrom, ops::Deref};
/// Sanitized transaction and the hash of its message
#[derive(Debug, Clone)]
pub struct SanitizedTransaction<'a> {
transaction: Cow<'a, Transaction>,
pub message_hash: Hash,
}
impl<'a> SanitizedTransaction<'a> {
pub fn try_create(transaction: Cow<'a, Transaction>, message_hash: Hash) -> Result<Self> {
transaction.sanitize()?;
if Self::has_duplicates(&transaction.message.account_keys) {
return Err(TransactionError::AccountLoadedTwice);
}
Ok(Self {
transaction,
message_hash,
})
}
/// Return true if the slice has any duplicate elements
pub fn has_duplicates<T: PartialEq>(xs: &[T]) -> bool {
// Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
// `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
// ~50 times faster than using HashSet for very short slices.
for i in 1..xs.len() {
#[allow(clippy::integer_arithmetic)]
if xs[i..].contains(&xs[i - 1]) {
return true;
}
}
false
}
}
impl Deref for SanitizedTransaction<'_> {
type Target = Transaction;
fn deref(&self) -> &Self::Target {
&self.transaction
}
}
impl<'a> TryFrom<Transaction> for SanitizedTransaction<'_> {
type Error = TransactionError;
fn try_from(transaction: Transaction) -> Result<Self> {
let message_hash = transaction.message().hash();
Self::try_create(Cow::Owned(transaction), message_hash)
}
}
impl<'a> TryFrom<&'a Transaction> for SanitizedTransaction<'a> {
type Error = TransactionError;
fn try_from(transaction: &'a Transaction) -> Result<Self> {
let message_hash = transaction.message().hash();
Self::try_create(Cow::Borrowed(transaction), message_hash)
}
}
pub trait SanitizedTransactionSlice<'a> {
fn as_transactions_iter(&'a self) -> Box<dyn Iterator<Item = &'a Transaction> + '_>;
}
impl<'a> SanitizedTransactionSlice<'a> for [SanitizedTransaction<'a>] {
fn as_transactions_iter(&'a self) -> Box<dyn Iterator<Item = &'a Transaction> + '_> {
Box::new(self.iter().map(Deref::deref))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_has_duplicates() {
assert!(!SanitizedTransaction::has_duplicates(&[1, 2]));
assert!(SanitizedTransaction::has_duplicates(&[1, 2, 1]));
}
}