Refactor: Improve type safety and readability of transaction execution (#22215)

* Refactor Bank::load_and_execute_transactions

* Refactor: improve type safety of TransactionExecutionResult

* Add enum for extra type safety in execution results

* feedback
This commit is contained in:
Justin Starry
2022-01-05 10:15:15 +08:00
committed by GitHub
parent e201b41341
commit 45458e7139
9 changed files with 635 additions and 505 deletions

View File

@@ -1102,26 +1102,35 @@ impl Accounts {
leave_nonce_on_success: bool,
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
let mut accounts = Vec::with_capacity(load_results.len());
for (i, ((tx_load_result, _), tx)) in load_results.iter_mut().zip(txs).enumerate() {
for (i, ((tx_load_result, nonce), tx)) in load_results.iter_mut().zip(txs).enumerate() {
if tx_load_result.is_err() {
// Don't store any accounts if tx failed to load
continue;
}
let (execution_result, nonce) = &execution_results[i];
let maybe_nonce = match (execution_result, nonce) {
(Ok(_), Some(nonce)) => {
let execution_status = match &execution_results[i] {
TransactionExecutionResult::Executed(details) => &details.status,
// Don't store any accounts if tx wasn't executed
TransactionExecutionResult::NotExecuted(_) => continue,
};
let maybe_nonce = match (execution_status, &*nonce) {
(Ok(()), Some(nonce)) => {
if leave_nonce_on_success {
None
} else {
Some((nonce, false /* rollback */))
}
}
(Err(TransactionError::InstructionError(_, _)), Some(nonce)) => {
(Err(_), Some(nonce)) => {
Some((nonce, true /* rollback */))
}
(Ok(_), _) => None, // Success, don't do any additional nonce processing
(Err(_), _) => continue, // Not nonce, don't store any accounts
(Ok(_), None) => None, // Success, don't do any additional nonce processing
(Err(_), None) => {
// Fees for failed transactions which don't use durable nonces are
// deducted in Bank::filter_program_errors_and_collect_fee
continue;
}
};
let message = tx.message();
@@ -1139,14 +1148,14 @@ impl Accounts {
let is_nonce_account = prepare_if_nonce_account(
address,
account,
execution_result,
execution_status,
is_fee_payer,
maybe_nonce,
blockhash,
lamports_per_signature,
);
if execution_result.is_ok() || is_nonce_account || is_fee_payer {
if execution_status.is_ok() || is_nonce_account || is_fee_payer {
if account.rent_epoch() == INITIAL_RENT_EPOCH {
let rent = rent_collector.collect_from_created_account(
address,
@@ -1255,7 +1264,10 @@ pub fn update_accounts_bench(accounts: &Accounts, pubkeys: &[Pubkey], slot: u64)
mod tests {
use {
super::*,
crate::rent_collector::RentCollector,
crate::{
bank::{DurableNonceFee, TransactionExecutionDetails},
rent_collector::RentCollector,
},
solana_sdk::{
account::{AccountSharedData, WritableAccount},
epoch_schedule::EpochSchedule,
@@ -1288,6 +1300,18 @@ mod tests {
))
}
fn new_execution_result(
status: Result<()>,
nonce: Option<&NonceFull>,
) -> TransactionExecutionResult {
TransactionExecutionResult::Executed(TransactionExecutionDetails {
status,
log_messages: None,
inner_instructions: None,
durable_nonce_fee: nonce.map(DurableNonceFee::from),
})
}
fn load_accounts_with_fee_and_rent(
tx: Transaction,
ka: &[TransactionAccount],
@@ -2698,10 +2722,10 @@ mod tests {
.insert_new_readonly(&pubkey);
}
let txs = vec![tx0, tx1];
let programs = vec![(Ok(()), None), (Ok(()), None)];
let execution_results = vec![new_execution_result(Ok(()), None); 2];
let collected_accounts = accounts.collect_accounts_to_store(
&txs,
&programs,
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&Hash::default(),
@@ -3121,16 +3145,16 @@ mod tests {
AccountShrinkThreshold::default(),
);
let txs = vec![tx];
let programs = vec![(
let execution_results = vec![new_execution_result(
Err(TransactionError::InstructionError(
1,
InstructionError::InvalidArgument,
)),
nonce,
nonce.as_ref(),
)];
let collected_accounts = accounts.collect_accounts_to_store(
&txs,
&programs,
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&next_blockhash,
@@ -3231,16 +3255,16 @@ mod tests {
AccountShrinkThreshold::default(),
);
let txs = vec![tx];
let programs = vec![(
let execution_results = vec![new_execution_result(
Err(TransactionError::InstructionError(
1,
InstructionError::InvalidArgument,
)),
nonce,
nonce.as_ref(),
)];
let collected_accounts = accounts.collect_accounts_to_store(
&txs,
&programs,
&execution_results,
loaded.as_mut_slice(),
&rent_collector,
&next_blockhash,

View File

@@ -37,7 +37,7 @@
use solana_sdk::recent_blockhashes_account;
use {
crate::{
accounts::{AccountAddressFilter, Accounts, TransactionLoadResult},
accounts::{AccountAddressFilter, Accounts, LoadedTransaction, TransactionLoadResult},
accounts_db::{
AccountShrinkThreshold, AccountsDbConfig, ErrorCounters, SnapshotStorages,
ACCOUNTS_DB_CONFIG_FOR_BENCHMARKS, ACCOUNTS_DB_CONFIG_FOR_TESTING,
@@ -504,12 +504,91 @@ impl StatusCacheRc {
}
pub type TransactionCheckResult = (Result<()>, Option<NoncePartial>);
pub type TransactionExecutionResult = (Result<()>, Option<NonceFull>);
pub struct TransactionResults {
pub fee_collection_results: Vec<Result<()>>,
pub execution_results: Vec<TransactionExecutionResult>,
pub rent_debits: Vec<RentDebits>,
}
#[derive(Debug, Clone)]
pub struct TransactionExecutionDetails {
pub status: Result<()>,
pub log_messages: Option<Vec<String>>,
pub inner_instructions: Option<Vec<Vec<CompiledInstruction>>>,
pub durable_nonce_fee: Option<DurableNonceFee>,
}
/// Type safe representation of a transaction execution attempt which
/// differentiates between a transaction that was executed (will be
/// committed to the ledger) and a transaction which wasn't executed
/// and will be dropped.
///
/// Note: `Result<TransactionExecutionDetails, TransactionError>` is not
/// used because it's easy to forget that the inner `details.status` field
/// is what should be checked to detect a successful transaction. This
/// enum provides a convenience method `Self::was_executed_successfully` to
/// make such checks hard to do incorrectly.
#[derive(Debug, Clone)]
pub enum TransactionExecutionResult {
Executed(TransactionExecutionDetails),
NotExecuted(TransactionError),
}
impl TransactionExecutionResult {
pub fn was_executed_successfully(&self) -> bool {
match self {
Self::Executed(details) => details.status.is_ok(),
Self::NotExecuted { .. } => false,
}
}
pub fn was_executed(&self) -> bool {
match self {
Self::Executed(_) => true,
Self::NotExecuted(_) => false,
}
}
pub fn details(&self) -> Option<&TransactionExecutionDetails> {
match self {
Self::Executed(details) => Some(details),
Self::NotExecuted(_) => None,
}
}
pub fn flattened_result(&self) -> Result<()> {
match self {
Self::Executed(details) => details.status.clone(),
Self::NotExecuted(err) => Err(err.clone()),
}
}
}
#[derive(Debug, Clone)]
pub enum DurableNonceFee {
Valid(u64),
Invalid,
}
impl From<&NonceFull> for DurableNonceFee {
fn from(nonce: &NonceFull) -> Self {
match nonce.lamports_per_signature() {
Some(lamports_per_signature) => Self::Valid(lamports_per_signature),
None => Self::Invalid,
}
}
}
impl DurableNonceFee {
pub fn lamports_per_signature(&self) -> Option<u64> {
match self {
Self::Valid(lamports_per_signature) => Some(*lamports_per_signature),
Self::Invalid => None,
}
}
}
pub struct TransactionSimulationResult {
pub result: Result<()>,
pub logs: TransactionLogMessages,
@@ -2993,30 +3072,22 @@ impl Bank {
.clear_slot_entries(slot);
}
pub fn can_commit(result: &Result<()>) -> bool {
match result {
Ok(_) => true,
Err(TransactionError::InstructionError(_, _)) => true,
Err(_) => false,
}
}
fn update_transaction_statuses(
&self,
sanitized_txs: &[SanitizedTransaction],
res: &[TransactionExecutionResult],
execution_results: &[TransactionExecutionResult],
) {
let mut status_cache = self.src.status_cache.write().unwrap();
assert_eq!(sanitized_txs.len(), res.len());
for (tx, (res, _nonce)) in sanitized_txs.iter().zip(res) {
if Self::can_commit(res) {
assert_eq!(sanitized_txs.len(), execution_results.len());
for (tx, execution_result) in sanitized_txs.iter().zip(execution_results) {
if let TransactionExecutionResult::Executed(details) = execution_result {
// Add the message hash to the status cache to ensure that this message
// won't be processed again with a different signature.
status_cache.insert(
tx.message().recent_blockhash(),
tx.message_hash(),
self.slot(),
res.clone(),
details.status.clone(),
);
// Add the transaction signature to the status cache so that transaction status
// can be queried by transaction signature over RPC. In the future, this should
@@ -3025,7 +3096,7 @@ impl Bank {
tx.message().recent_blockhash(),
tx.signature(),
self.slot(),
res.clone(),
details.status.clone(),
);
}
}
@@ -3161,9 +3232,7 @@ impl Bank {
let (
loaded_transactions,
executed,
_inner_instructions,
logs,
mut execution_results,
_retryable_transactions,
_transaction_count,
_signature_count,
@@ -3178,8 +3247,6 @@ impl Bank {
&mut timings,
);
let result = executed[0].0.clone().map(|_| ());
let logs = logs.get(0).cloned().flatten().unwrap_or_default();
let post_simulation_accounts = loaded_transactions
.into_iter()
.next()
@@ -3205,8 +3272,16 @@ impl Bank {
debug!("simulate_transaction: {:?}", timings);
let execution_result = execution_results.pop().unwrap();
let flattened_result = execution_result.flattened_result();
let logs = match execution_result {
TransactionExecutionResult::Executed(details) => details.log_messages,
TransactionExecutionResult::NotExecuted(_) => None,
}
.unwrap_or_default();
TransactionSimulationResult {
result,
result: flattened_result,
logs,
post_simulation_accounts,
units_consumed,
@@ -3476,6 +3551,106 @@ impl Bank {
cache.remove(pubkey);
}
/// Execute a transaction using the provided loaded accounts and update
/// the executors cache if the transaction was successful.
fn execute_loaded_transaction(
&self,
tx: &SanitizedTransaction,
loaded_transaction: &mut LoadedTransaction,
compute_budget: ComputeBudget,
durable_nonce_fee: Option<DurableNonceFee>,
enable_cpi_recording: bool,
enable_log_recording: bool,
execute_details_timings: &mut ExecuteDetailsTimings,
error_counters: &mut ErrorCounters,
) -> TransactionExecutionResult {
let legacy_message = match tx.message().legacy_message() {
Some(message) => message,
None => {
// TODO: support versioned messages
return TransactionExecutionResult::NotExecuted(
TransactionError::UnsupportedVersion,
);
}
};
let executors = self.get_executors(
tx.message(),
&loaded_transaction.accounts,
&loaded_transaction.program_indices,
);
let mut transaction_accounts = Vec::new();
std::mem::swap(&mut loaded_transaction.accounts, &mut transaction_accounts);
let mut transaction_context = TransactionContext::new(
transaction_accounts,
compute_budget.max_invoke_depth.saturating_add(1),
);
let instruction_recorder = if enable_cpi_recording {
Some(InstructionRecorder::new_ref(
tx.message().instructions().len(),
))
} else {
None
};
let log_collector = if enable_log_recording {
Some(LogCollector::new_ref())
} else {
None
};
let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature();
let process_result = MessageProcessor::process_message(
&self.builtin_programs.vec,
legacy_message,
&loaded_transaction.program_indices,
&mut transaction_context,
self.rent_collector.rent,
log_collector.clone(),
executors.clone(),
instruction_recorder.clone(),
self.feature_set.clone(),
compute_budget,
execute_details_timings,
&*self.sysvar_cache.read().unwrap(),
blockhash,
lamports_per_signature,
self.load_accounts_data_len(),
);
let log_messages: Option<TransactionLogMessages> =
log_collector.and_then(|log_collector| {
Rc::try_unwrap(log_collector)
.map(|log_collector| log_collector.into_inner().into())
.ok()
});
let inner_instructions = instruction_recorder
.and_then(|instruction_recorder| Rc::try_unwrap(instruction_recorder).ok())
.map(|instruction_recorder| instruction_recorder.into_inner().deconstruct());
loaded_transaction.accounts = transaction_context.deconstruct();
let status = process_result
.map(|info| {
self.store_accounts_data_len(info.accounts_data_len);
self.update_executors(executors);
})
.map_err(|err| {
error_counters.instruction_error += 1;
err
});
TransactionExecutionResult::Executed(TransactionExecutionDetails {
status,
log_messages,
inner_instructions,
durable_nonce_fee,
})
}
#[allow(clippy::type_complexity)]
pub fn load_and_execute_transactions(
&self,
@@ -3487,8 +3662,6 @@ impl Bank {
) -> (
Vec<TransactionLoadResult>,
Vec<TransactionExecutionResult>,
Vec<Option<InnerInstructionsList>>,
Vec<Option<TransactionLogMessages>>,
Vec<usize>,
u64,
u64,
@@ -3538,126 +3711,36 @@ 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(sanitized_txs.len());
let mut transaction_log_messages: Vec<Option<Vec<String>>> =
Vec::with_capacity(sanitized_txs.len());
let executed: Vec<TransactionExecutionResult> = loaded_txs
let execute_details_timings = &mut timings.details;
let execution_results: Vec<TransactionExecutionResult> = loaded_txs
.iter_mut()
.zip(sanitized_txs.iter())
.map(|(accs, tx)| match accs {
(Err(e), _nonce) => {
transaction_log_messages.push(None);
inner_instructions.push(None);
(Err(e.clone()), None)
}
(Err(e), _nonce) => TransactionExecutionResult::NotExecuted(e.clone()),
(Ok(loaded_transaction), nonce) => {
let feature_set = self.feature_set.clone();
signature_count += u64::from(tx.message().header().num_required_signatures);
let mut compute_budget = self.compute_budget.unwrap_or_else(ComputeBudget::new);
let mut process_result = if feature_set.is_active(&tx_wide_compute_cap::id()) {
compute_budget.process_transaction(tx, feature_set.clone())
} else {
Ok(())
};
if process_result.is_ok() {
let executors = self.get_executors(
tx.message(),
&loaded_transaction.accounts,
&loaded_transaction.program_indices,
);
let mut transaction_accounts = Vec::new();
std::mem::swap(&mut loaded_transaction.accounts, &mut transaction_accounts);
let mut transaction_context = TransactionContext::new(
transaction_accounts,
compute_budget.max_invoke_depth.saturating_add(1),
);
let instruction_recorder = if enable_cpi_recording {
Some(InstructionRecorder::new_ref(
tx.message().instructions().len(),
))
} else {
None
};
let log_collector = if enable_log_recording {
Some(LogCollector::new_ref())
} else {
None
};
let (blockhash, lamports_per_signature) =
self.last_blockhash_and_lamports_per_signature();
if let Some(legacy_message) = tx.message().legacy_message() {
process_result = MessageProcessor::process_message(
&self.builtin_programs.vec,
legacy_message,
&loaded_transaction.program_indices,
&mut transaction_context,
self.rent_collector.rent,
log_collector.clone(),
executors.clone(),
instruction_recorder.clone(),
feature_set,
compute_budget,
&mut timings.details,
&*self.sysvar_cache.read().unwrap(),
blockhash,
lamports_per_signature,
self.load_accounts_data_len(),
)
.map(|process_result| {
self.store_accounts_data_len(process_result.accounts_data_len)
});
} else {
// TODO: support versioned messages
process_result = Err(TransactionError::UnsupportedVersion);
if feature_set.is_active(&tx_wide_compute_cap::id()) {
if let Err(err) = compute_budget.process_transaction(tx, feature_set) {
return TransactionExecutionResult::NotExecuted(err);
}
let log_messages: Option<TransactionLogMessages> =
log_collector.and_then(|log_collector| {
Rc::try_unwrap(log_collector)
.map(|log_collector| log_collector.into_inner().into())
.ok()
});
transaction_log_messages.push(log_messages);
inner_instructions.push(
instruction_recorder
.and_then(|instruction_recorder| {
Rc::try_unwrap(instruction_recorder).ok()
})
.map(|instruction_recorder| {
instruction_recorder.into_inner().deconstruct()
}),
);
loaded_transaction.accounts = transaction_context.deconstruct();
if process_result.is_ok() {
self.update_executors(executors);
}
} else {
transaction_log_messages.push(None);
inner_instructions.push(None);
}
let nonce = match &process_result {
Ok(_) => nonce.clone(), // May need to calculate the fee based on the nonce
Err(TransactionError::InstructionError(_, _)) => {
error_counters.instruction_error += 1;
nonce.clone() // May need to advance the nonce
}
_ => None,
};
let durable_nonce_fee = nonce.as_ref().map(DurableNonceFee::from);
(process_result, nonce)
self.execute_loaded_transaction(
tx,
loaded_transaction,
compute_budget,
durable_nonce_fee,
enable_cpi_recording,
enable_log_recording,
execute_details_timings,
&mut error_counters,
)
}
})
.collect();
@@ -3680,17 +3763,18 @@ impl Bank {
let transaction_log_collector_config =
self.transaction_log_collector_config.read().unwrap();
for (i, ((r, _nonce), tx)) in executed.iter().zip(sanitized_txs).enumerate() {
for (execution_result, tx) in execution_results.iter().zip(sanitized_txs) {
if let Some(debug_keys) = &self.transaction_debug_keys {
for key in tx.message().account_keys_iter() {
if debug_keys.contains(key) {
info!("slot: {} result: {:?} tx: {:?}", self.slot, r, tx);
let result = execution_result.flattened_result();
info!("slot: {} result: {:?} tx: {:?}", self.slot, result, tx);
break;
}
}
}
if Self::can_commit(r) // Skip log collection for unprocessed transactions
if execution_result.was_executed() // Skip log collection for unprocessed transactions
&& transaction_log_collector_config.filter != TransactionLogCollectorFilter::None
{
let mut filtered_mentioned_addresses = Vec::new();
@@ -3721,16 +3805,21 @@ impl Bank {
};
if store {
if let Some(log_messages) = transaction_log_messages.get(i).cloned().flatten() {
if let TransactionExecutionResult::Executed(TransactionExecutionDetails {
status,
log_messages: Some(log_messages),
..
}) = execution_result
{
let mut transaction_log_collector =
self.transaction_log_collector.write().unwrap();
let transaction_log_index = transaction_log_collector.logs.len();
transaction_log_collector.logs.push(TransactionLogInfo {
signature: *tx.signature(),
result: r.clone(),
result: status.clone(),
is_vote,
log_messages,
log_messages: log_messages.clone(),
});
for key in filtered_mentioned_addresses.into_iter() {
transaction_log_collector
@@ -3743,13 +3832,16 @@ impl Bank {
}
}
if r.is_ok() {
tx_count += 1;
} else {
if *err_count == 0 {
debug!("tx error: {:?} {:?}", r, tx);
match execution_result.flattened_result() {
Ok(()) => {
tx_count += 1;
}
Err(err) => {
if *err_count == 0 {
debug!("tx error: {:?} {:?}", err, tx);
}
*err_count += 1;
}
*err_count += 1;
}
}
if *err_count > 0 {
@@ -3762,9 +3854,7 @@ impl Bank {
Self::update_error_counters(&error_counters);
(
loaded_txs,
executed,
inner_instructions,
transaction_log_messages,
execution_results,
retryable_txs,
tx_count,
signature_count,
@@ -3806,10 +3896,16 @@ impl Bank {
let results = txs
.iter()
.zip(execution_results)
.map(|(tx, (execution_result, nonce))| {
let (lamports_per_signature, is_nonce) = nonce
.as_ref()
.map(|nonce| nonce.lamports_per_signature())
.map(|(tx, execution_result)| {
let (execution_status, durable_nonce_fee) = match &execution_result {
TransactionExecutionResult::Executed(details) => {
Ok((&details.status, details.durable_nonce_fee.as_ref()))
}
TransactionExecutionResult::NotExecuted(err) => Err(err.clone()),
}?;
let (lamports_per_signature, is_nonce) = durable_nonce_fee
.map(|durable_nonce_fee| durable_nonce_fee.lamports_per_signature())
.map(|maybe_lamports_per_signature| (maybe_lamports_per_signature, true))
.unwrap_or_else(|| {
(
@@ -3822,27 +3918,19 @@ impl Bank {
lamports_per_signature.ok_or(TransactionError::BlockhashNotFound)?;
let fee = Self::calculate_fee(tx.message(), lamports_per_signature);
match *execution_result {
Err(TransactionError::InstructionError(_, _)) => {
// In case of instruction error, even though no accounts
// were stored we still need to charge the payer the
// fee.
//
//...except nonce accounts, which already have their
// post-load, fee deducted, pre-execute account state
// stored
if !is_nonce {
self.withdraw(tx.message().fee_payer(), fee)?;
}
fees += fee;
Ok(())
}
Ok(()) => {
fees += fee;
Ok(())
}
_ => execution_result.clone(),
// In case of instruction error, even though no accounts
// were stored we still need to charge the payer the
// fee.
//
//...except nonce accounts, which already have their
// post-load, fee deducted, pre-execute account state
// stored
if execution_status.is_err() && !is_nonce {
self.withdraw(tx.message().fee_payer(), fee)?;
}
fees += fee;
Ok(())
})
.collect();
@@ -3854,7 +3942,7 @@ impl Bank {
&self,
sanitized_txs: &[SanitizedTransaction],
loaded_txs: &mut [TransactionLoadResult],
executed_results: &[TransactionExecutionResult],
execution_results: Vec<TransactionExecutionResult>,
tx_count: u64,
signature_count: u64,
timings: &mut ExecuteTimings,
@@ -3880,10 +3968,7 @@ impl Bank {
.fetch_max(processed_tx_count, Relaxed);
}
if executed_results
.iter()
.any(|(res, _)| Self::can_commit(res))
{
if execution_results.iter().any(|result| result.was_executed()) {
self.is_delta.store(true, Relaxed);
}
@@ -3892,7 +3977,7 @@ impl Bank {
self.rc.accounts.store_cached(
self.slot(),
sanitized_txs,
executed_results,
&execution_results,
loaded_txs,
&self.rent_collector,
&blockhash,
@@ -3900,10 +3985,10 @@ impl Bank {
self.rent_for_sysvars(),
self.leave_nonce_on_success(),
);
let rent_debits = self.collect_rent(executed_results, loaded_txs);
let rent_debits = self.collect_rent(&execution_results, loaded_txs);
let mut update_stakes_cache_time = Measure::start("update_stakes_cache_time");
self.update_stakes_cache(sanitized_txs, executed_results, loaded_txs);
self.update_stakes_cache(sanitized_txs, &execution_results, loaded_txs);
update_stakes_cache_time.stop();
// once committed there is no way to unroll
@@ -3917,13 +4002,13 @@ impl Bank {
timings.update_stakes_cache_us = timings
.update_stakes_cache_us
.saturating_add(update_stakes_cache_time.as_us());
self.update_transaction_statuses(sanitized_txs, executed_results);
self.update_transaction_statuses(sanitized_txs, &execution_results);
let fee_collection_results =
self.filter_program_errors_and_collect_fee(sanitized_txs, executed_results);
self.filter_program_errors_and_collect_fee(sanitized_txs, &execution_results);
TransactionResults {
fee_collection_results,
execution_results: executed_results.to_vec(),
execution_results,
rent_debits,
}
}
@@ -4087,24 +4172,24 @@ impl Bank {
fn collect_rent(
&self,
res: &[TransactionExecutionResult],
execution_results: &[TransactionExecutionResult],
loaded_txs: &mut [TransactionLoadResult],
) -> Vec<RentDebits> {
let mut collected_rent: u64 = 0;
let mut rent_debits: Vec<RentDebits> = Vec::with_capacity(loaded_txs.len());
for (i, (raccs, _nonce)) in loaded_txs.iter_mut().enumerate() {
let (res, _nonce) = &res[i];
if res.is_err() || raccs.is_err() {
rent_debits.push(RentDebits::default());
continue;
}
let loaded_transaction = raccs.as_mut().unwrap();
collected_rent += loaded_transaction.rent;
rent_debits.push(mem::take(&mut loaded_transaction.rent_debits));
}
let rent_debits: Vec<_> = loaded_txs
.iter_mut()
.zip(execution_results)
.map(|((load_result, _nonce), execution_result)| {
if let (Ok(loaded_transaction), true) =
(load_result, execution_result.was_executed_successfully())
{
collected_rent += loaded_transaction.rent;
mem::take(&mut loaded_transaction.rent_debits)
} else {
RentDebits::default()
}
})
.collect();
self.collected_rent.fetch_add(collected_rent, Relaxed);
rent_debits
}
@@ -4601,38 +4686,26 @@ impl Bank {
enable_cpi_recording: bool,
enable_log_recording: bool,
timings: &mut ExecuteTimings,
) -> (
TransactionResults,
TransactionBalancesSet,
Vec<Option<InnerInstructionsList>>,
Vec<Option<TransactionLogMessages>>,
) {
) -> (TransactionResults, TransactionBalancesSet) {
let pre_balances = if collect_balances {
self.collect_balances(batch)
} else {
vec![]
};
let (
mut loaded_txs,
executed,
inner_instructions,
transaction_logs,
_,
tx_count,
signature_count,
) = self.load_and_execute_transactions(
batch,
max_age,
enable_cpi_recording,
enable_log_recording,
timings,
);
let (mut loaded_txs, execution_results, _, tx_count, signature_count) = self
.load_and_execute_transactions(
batch,
max_age,
enable_cpi_recording,
enable_log_recording,
timings,
);
let results = self.commit_transactions(
batch.sanitized_transactions(),
&mut loaded_txs,
&executed,
execution_results,
tx_count,
signature_count,
timings,
@@ -4645,8 +4718,6 @@ impl Bank {
(
results,
TransactionBalancesSet::new(pre_balances, post_balances),
inner_instructions,
transaction_logs,
)
}
@@ -5485,22 +5556,20 @@ impl Bank {
fn update_stakes_cache(
&self,
txs: &[SanitizedTransaction],
res: &[TransactionExecutionResult],
execution_results: &[TransactionExecutionResult],
loaded_txs: &[TransactionLoadResult],
) {
for (i, ((raccs, _load_nonce), tx)) in loaded_txs.iter().zip(txs).enumerate() {
let (res, _res_nonce) = &res[i];
if res.is_err() || raccs.is_err() {
continue;
}
let message = tx.message();
let loaded_transaction = raccs.as_ref().unwrap();
for (_i, (pubkey, account)) in
(0..message.account_keys_len()).zip(loaded_transaction.accounts.iter())
{
self.stakes_cache.check_and_store(pubkey, account);
for (i, ((load_result, _load_nonce), tx)) in loaded_txs.iter().zip(txs).enumerate() {
if let (Ok(loaded_transaction), true) = (
load_result,
execution_results[i].was_executed_successfully(),
) {
let message = tx.message();
for (_i, (pubkey, account)) in
(0..message.account_keys_len()).zip(loaded_transaction.accounts.iter())
{
self.stakes_cache.check_and_store(pubkey, account);
}
}
}
}
@@ -6255,6 +6324,18 @@ pub(crate) mod tests {
Message::new(instructions, payer).try_into().unwrap()
}
fn new_execution_result(
status: Result<()>,
nonce: Option<&NonceFull>,
) -> TransactionExecutionResult {
TransactionExecutionResult::Executed(TransactionExecutionDetails {
status,
log_messages: None,
inner_instructions: None,
durable_nonce_fee: nonce.map(DurableNonceFee::from),
})
}
#[test]
fn test_nonce_info() {
let lamports_per_signature = 42;
@@ -8993,8 +9074,8 @@ pub(crate) mod tests {
));
let results = vec![
(Ok(()), None),
(
new_execution_result(Ok(()), None),
new_execution_result(
Err(TransactionError::InstructionError(
1,
SystemError::ResultWithNegativeLamports.into(),
@@ -11362,8 +11443,8 @@ pub(crate) mod tests {
let txs = vec![tx0, tx1, tx2];
let lock_result = bank0.prepare_batch_for_tests(txs);
let (transaction_results, transaction_balances_set, inner_instructions, transaction_logs) =
bank0.load_execute_and_commit_transactions(
let (transaction_results, transaction_balances_set) = bank0
.load_execute_and_commit_transactions(
&lock_result,
MAX_PROCESSING_AGE,
true,
@@ -11372,27 +11453,34 @@ pub(crate) mod tests {
&mut ExecuteTimings::default(),
);
assert!(inner_instructions.iter().all(Option::is_none));
assert!(transaction_logs.iter().all(Option::is_none));
assert_eq!(inner_instructions.len(), 3);
assert_eq!(transaction_logs.len(), 3);
assert_eq!(transaction_balances_set.pre_balances.len(), 3);
assert_eq!(transaction_balances_set.post_balances.len(), 3);
assert!(transaction_results.execution_results[0].0.is_ok());
assert!(transaction_results.execution_results[0].was_executed_successfully());
assert_eq!(transaction_balances_set.pre_balances[0], vec![8, 11, 1]);
assert_eq!(transaction_balances_set.post_balances[0], vec![5, 13, 1]);
// Failed transactions still produce balance sets
// This is a TransactionError - not possible to charge fees
assert!(transaction_results.execution_results[1].0.is_err());
assert!(matches!(
transaction_results.execution_results[1],
TransactionExecutionResult::NotExecuted(TransactionError::AccountNotFound),
));
assert_eq!(transaction_balances_set.pre_balances[1], vec![0, 0, 1]);
assert_eq!(transaction_balances_set.post_balances[1], vec![0, 0, 1]);
// Failed transactions still produce balance sets
// This is an InstructionError - fees charged
assert!(transaction_results.execution_results[2].0.is_err());
assert!(matches!(
transaction_results.execution_results[2],
TransactionExecutionResult::Executed(TransactionExecutionDetails {
status: Err(TransactionError::InstructionError(
0,
InstructionError::Custom(1),
)),
..
}),
));
assert_eq!(transaction_balances_set.pre_balances[2], vec![9, 0, 1]);
assert_eq!(transaction_balances_set.post_balances[2], vec![8, 0, 1]);
}
@@ -14497,7 +14585,7 @@ pub(crate) mod tests {
let txs = vec![tx0, tx1, tx2];
let batch = bank.prepare_batch_for_tests(txs);
let log_results = bank
let execution_results = bank
.load_execute_and_commit_transactions(
&batch,
MAX_PROCESSING_AGE,
@@ -14506,11 +14594,28 @@ pub(crate) mod tests {
true,
&mut ExecuteTimings::default(),
)
.3;
assert_eq!(log_results.len(), 3);
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[2].as_ref().is_none());
.0
.execution_results;
assert_eq!(execution_results.len(), 3);
assert!(execution_results[0].details().is_some());
assert!(execution_results[0]
.details()
.unwrap()
.log_messages
.as_ref()
.unwrap()[1]
.contains(&"success".to_string()));
assert!(execution_results[1].details().is_some());
assert!(execution_results[1]
.details()
.unwrap()
.log_messages
.as_ref()
.unwrap()[2]
.contains(&"failed".to_string()));
assert!(!execution_results[2].was_executed());
let stored_logs = &bank.transaction_log_collector.read().unwrap().logs;
let success_log_info = stored_logs

View File

@@ -43,8 +43,8 @@ pub fn find_and_send_votes(
sanitized_txs
.iter()
.zip(execution_results.iter())
.for_each(|(tx, (result, _nonce))| {
if tx.is_simple_vote_transaction() && result.is_ok() {
.for_each(|(tx, result)| {
if tx.is_simple_vote_transaction() && result.was_executed_successfully() {
if let Some(parsed_vote) =
vote_transaction::parse_sanitized_vote_transaction(tx)
{