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:
parent
e201b41341
commit
45458e7139
|
@ -794,16 +794,17 @@ impl BankingStage {
|
|||
fn record_transactions(
|
||||
bank_slot: Slot,
|
||||
txs: &[SanitizedTransaction],
|
||||
results: &[TransactionExecutionResult],
|
||||
execution_results: &[TransactionExecutionResult],
|
||||
recorder: &TransactionRecorder,
|
||||
) -> (Result<usize, PohRecorderError>, Vec<usize>) {
|
||||
let mut processed_generation = Measure::start("record::process_generation");
|
||||
let (processed_transactions, processed_transactions_indexes): (Vec<_>, Vec<_>) = results
|
||||
let (processed_transactions, processed_transactions_indexes): (Vec<_>, Vec<_>) =
|
||||
execution_results
|
||||
.iter()
|
||||
.zip(txs)
|
||||
.enumerate()
|
||||
.filter_map(|(i, ((r, _n), tx))| {
|
||||
if Bank::can_commit(r) {
|
||||
.filter_map(|(i, (execution_result, tx))| {
|
||||
if execution_result.was_executed() {
|
||||
Some((tx.to_versioned_transaction(), i))
|
||||
} else {
|
||||
None
|
||||
|
@ -875,15 +876,8 @@ impl BankingStage {
|
|||
};
|
||||
|
||||
let mut execute_timings = ExecuteTimings::default();
|
||||
let (
|
||||
mut loaded_accounts,
|
||||
results,
|
||||
inner_instructions,
|
||||
transaction_logs,
|
||||
mut retryable_txs,
|
||||
tx_count,
|
||||
signature_count,
|
||||
) = bank.load_and_execute_transactions(
|
||||
let (mut loaded_accounts, execution_results, mut retryable_txs, tx_count, signature_count) =
|
||||
bank.load_and_execute_transactions(
|
||||
batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
transaction_status_sender.is_some(),
|
||||
|
@ -895,8 +889,12 @@ impl BankingStage {
|
|||
let freeze_lock = bank.freeze_lock();
|
||||
|
||||
let mut record_time = Measure::start("record_time");
|
||||
let (num_to_commit, retryable_record_txs) =
|
||||
Self::record_transactions(bank.slot(), batch.sanitized_transactions(), &results, poh);
|
||||
let (num_to_commit, retryable_record_txs) = Self::record_transactions(
|
||||
bank.slot(),
|
||||
batch.sanitized_transactions(),
|
||||
&execution_results,
|
||||
poh,
|
||||
);
|
||||
inc_new_counter_info!(
|
||||
"banking_stage-record_transactions_num_to_commit",
|
||||
*num_to_commit.as_ref().unwrap_or(&0)
|
||||
|
@ -918,7 +916,7 @@ impl BankingStage {
|
|||
let tx_results = bank.commit_transactions(
|
||||
sanitized_txs,
|
||||
&mut loaded_accounts,
|
||||
&results,
|
||||
execution_results,
|
||||
tx_count,
|
||||
signature_count,
|
||||
&mut execute_timings,
|
||||
|
@ -935,8 +933,6 @@ impl BankingStage {
|
|||
tx_results.execution_results,
|
||||
TransactionBalancesSet::new(pre_balances, post_balances),
|
||||
TransactionTokenBalancesSet::new(pre_token_balances, post_token_balances),
|
||||
inner_instructions,
|
||||
transaction_logs,
|
||||
tx_results.rent_debits,
|
||||
);
|
||||
}
|
||||
|
@ -1491,6 +1487,7 @@ mod tests {
|
|||
poh_service::PohService,
|
||||
},
|
||||
solana_rpc::transaction_status_service::TransactionStatusService,
|
||||
solana_runtime::bank::TransactionExecutionDetails,
|
||||
solana_sdk::{
|
||||
hash::Hash,
|
||||
instruction::InstructionError,
|
||||
|
@ -1522,6 +1519,15 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
fn new_execution_result(status: Result<(), TransactionError>) -> TransactionExecutionResult {
|
||||
TransactionExecutionResult::Executed(TransactionExecutionDetails {
|
||||
status,
|
||||
log_messages: None,
|
||||
inner_instructions: None,
|
||||
durable_nonce_fee: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_banking_stage_shutdown1() {
|
||||
let genesis_config = create_genesis_config(2).genesis_config;
|
||||
|
@ -1913,19 +1919,16 @@ mod tests {
|
|||
system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_config.hash()),
|
||||
]);
|
||||
|
||||
let mut results = vec![(Ok(()), None), (Ok(()), None)];
|
||||
let mut results = vec![new_execution_result(Ok(())); 2];
|
||||
let _ = BankingStage::record_transactions(bank.slot(), &txs, &results, &recorder);
|
||||
let (_bank, (entry, _tick_height)) = entry_receiver.recv().unwrap();
|
||||
assert_eq!(entry.transactions.len(), txs.len());
|
||||
|
||||
// InstructionErrors should still be recorded
|
||||
results[0] = (
|
||||
Err(TransactionError::InstructionError(
|
||||
results[0] = new_execution_result(Err(TransactionError::InstructionError(
|
||||
1,
|
||||
SystemError::ResultWithNegativeLamports.into(),
|
||||
)),
|
||||
None,
|
||||
);
|
||||
)));
|
||||
let (res, retryable) =
|
||||
BankingStage::record_transactions(bank.slot(), &txs, &results, &recorder);
|
||||
res.unwrap();
|
||||
|
@ -1934,7 +1937,7 @@ mod tests {
|
|||
assert_eq!(entry.transactions.len(), txs.len());
|
||||
|
||||
// Other TransactionErrors should not be recorded
|
||||
results[0] = (Err(TransactionError::AccountNotFound), None);
|
||||
results[0] = TransactionExecutionResult::NotExecuted(TransactionError::AccountNotFound);
|
||||
let (res, retryable) =
|
||||
BankingStage::record_transactions(bank.slot(), &txs, &results, &recorder);
|
||||
res.unwrap();
|
||||
|
|
|
@ -20,8 +20,8 @@ use {
|
|||
accounts_index::AccountSecondaryIndexes,
|
||||
accounts_update_notifier_interface::AccountsUpdateNotifier,
|
||||
bank::{
|
||||
Bank, ExecuteTimings, InnerInstructionsList, RentDebits, TransactionBalancesSet,
|
||||
TransactionExecutionResult, TransactionLogMessages, TransactionResults,
|
||||
Bank, ExecuteTimings, RentDebits, TransactionBalancesSet, TransactionExecutionResult,
|
||||
TransactionResults,
|
||||
},
|
||||
bank_forks::BankForks,
|
||||
bank_utils,
|
||||
|
@ -175,8 +175,7 @@ fn execute_batch(
|
|||
|
||||
let pre_process_units: u64 = aggregate_total_execution_units(timings);
|
||||
|
||||
let (tx_results, balances, inner_instructions, transaction_logs) =
|
||||
batch.bank().load_execute_and_commit_transactions(
|
||||
let (tx_results, balances) = batch.bank().load_execute_and_commit_transactions(
|
||||
batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
transaction_status_sender.is_some(),
|
||||
|
@ -238,8 +237,6 @@ fn execute_batch(
|
|||
execution_results,
|
||||
balances,
|
||||
token_balances,
|
||||
inner_instructions,
|
||||
transaction_logs,
|
||||
rent_debits,
|
||||
);
|
||||
}
|
||||
|
@ -1399,11 +1396,9 @@ pub enum TransactionStatusMessage {
|
|||
pub struct TransactionStatusBatch {
|
||||
pub bank: Arc<Bank>,
|
||||
pub transactions: Vec<SanitizedTransaction>,
|
||||
pub statuses: Vec<TransactionExecutionResult>,
|
||||
pub execution_results: Vec<TransactionExecutionResult>,
|
||||
pub balances: TransactionBalancesSet,
|
||||
pub token_balances: TransactionTokenBalancesSet,
|
||||
pub inner_instructions: Option<Vec<Option<InnerInstructionsList>>>,
|
||||
pub transaction_logs: Option<Vec<Option<TransactionLogMessages>>>,
|
||||
pub rent_debits: Vec<RentDebits>,
|
||||
}
|
||||
|
||||
|
@ -1418,29 +1413,28 @@ impl TransactionStatusSender {
|
|||
&self,
|
||||
bank: Arc<Bank>,
|
||||
transactions: Vec<SanitizedTransaction>,
|
||||
statuses: Vec<TransactionExecutionResult>,
|
||||
mut execution_results: Vec<TransactionExecutionResult>,
|
||||
balances: TransactionBalancesSet,
|
||||
token_balances: TransactionTokenBalancesSet,
|
||||
inner_instructions: Vec<Option<InnerInstructionsList>>,
|
||||
transaction_logs: Vec<Option<TransactionLogMessages>>,
|
||||
rent_debits: Vec<RentDebits>,
|
||||
) {
|
||||
let slot = bank.slot();
|
||||
let (inner_instructions, transaction_logs) = if !self.enable_cpi_and_log_storage {
|
||||
(None, None)
|
||||
} else {
|
||||
(Some(inner_instructions), Some(transaction_logs))
|
||||
};
|
||||
if !self.enable_cpi_and_log_storage {
|
||||
execution_results.iter_mut().for_each(|execution_result| {
|
||||
if let TransactionExecutionResult::Executed(details) = execution_result {
|
||||
details.log_messages.take();
|
||||
details.inner_instructions.take();
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Err(e) = self
|
||||
.sender
|
||||
.send(TransactionStatusMessage::Batch(TransactionStatusBatch {
|
||||
bank,
|
||||
transactions,
|
||||
statuses,
|
||||
execution_results,
|
||||
balances,
|
||||
token_balances,
|
||||
inner_instructions,
|
||||
transaction_logs,
|
||||
rent_debits,
|
||||
}))
|
||||
{
|
||||
|
@ -3483,8 +3477,6 @@ pub mod tests {
|
|||
..
|
||||
},
|
||||
_balances,
|
||||
_inner_instructions,
|
||||
_log_messages,
|
||||
) = batch.bank().load_execute_and_commit_transactions(
|
||||
&batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
|
|
|
@ -397,7 +397,7 @@ fn setup_fees(bank: Bank) -> Bank {
|
|||
bank.commit_transactions(
|
||||
&[], // transactions
|
||||
&mut [], // loaded accounts
|
||||
&[], // transaction execution results
|
||||
vec![], // transaction execution results
|
||||
0, // tx count
|
||||
1, // signature count
|
||||
&mut ExecuteTimings::default(),
|
||||
|
|
|
@ -211,7 +211,10 @@ fn bench_create_vm(bencher: &mut Bencher) {
|
|||
// Serialize account data
|
||||
let (mut serialized, account_lengths) = serialize_parameters(
|
||||
invoke_context.transaction_context,
|
||||
invoke_context.transaction_context.get_current_instruction_context().unwrap(),
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.get_current_instruction_context()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -249,7 +252,10 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
|
|||
// Serialize account data
|
||||
let (mut serialized, account_lengths) = serialize_parameters(
|
||||
invoke_context.transaction_context,
|
||||
invoke_context.transaction_context.get_current_instruction_context().unwrap(),
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.get_current_instruction_context()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ use solana_bpf_loader_program::{
|
|||
use solana_bpf_rust_invoke::instructions::*;
|
||||
use solana_bpf_rust_realloc::instructions::*;
|
||||
use solana_bpf_rust_realloc_invoke::instructions::*;
|
||||
use solana_cli_output::display::println_transaction;
|
||||
use solana_program_runtime::invoke_context::with_mock_invoke_context;
|
||||
use solana_rbpf::{
|
||||
elf::Executable,
|
||||
|
@ -25,7 +24,10 @@ use solana_rbpf::{
|
|||
vm::{Config, Tracer},
|
||||
};
|
||||
use solana_runtime::{
|
||||
bank::{Bank, ExecuteTimings, NonceInfo, TransactionBalancesSet, TransactionResults},
|
||||
bank::{
|
||||
Bank, DurableNonceFee, ExecuteTimings, TransactionBalancesSet, TransactionExecutionDetails,
|
||||
TransactionExecutionResult, TransactionResults,
|
||||
},
|
||||
bank_client::BankClient,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
loader_utils::{
|
||||
|
@ -52,13 +54,10 @@ use solana_sdk::{
|
|||
transaction::{SanitizedTransaction, Transaction, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::{
|
||||
token_balances::collect_token_balances, ConfirmedTransactionWithStatusMeta, Encodable,
|
||||
InnerInstructions, TransactionStatusMeta, TransactionWithStatusMeta, UiTransactionEncoding,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap, convert::TryFrom, env, fs::File, io::Read, path::PathBuf, str::FromStr,
|
||||
sync::Arc,
|
||||
token_balances::collect_token_balances, ConfirmedTransactionWithStatusMeta, InnerInstructions,
|
||||
TransactionStatusMeta, TransactionWithStatusMeta,
|
||||
};
|
||||
use std::{collections::HashMap, env, fs::File, io::Read, path::PathBuf, str::FromStr, sync::Arc};
|
||||
|
||||
/// BPF program file extension
|
||||
const PLATFORM_FILE_EXTENSION_BPF: &str = "so";
|
||||
|
@ -198,7 +197,10 @@ fn run_program(name: &str) -> u64 {
|
|||
with_mock_invoke_context(loader_id, 0, |invoke_context| {
|
||||
let (parameter_bytes, account_lengths) = serialize_parameters(
|
||||
invoke_context.transaction_context,
|
||||
invoke_context.transaction_context.get_current_instruction_context().unwrap(),
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.get_current_instruction_context()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -224,7 +226,13 @@ fn run_program(name: &str) -> u64 {
|
|||
let mut instruction_count = 0;
|
||||
let mut tracer = None;
|
||||
for i in 0..2 {
|
||||
invoke_context.return_data = (*invoke_context.transaction_context.get_program_key().unwrap(), Vec::new());
|
||||
invoke_context.return_data = (
|
||||
*invoke_context
|
||||
.transaction_context
|
||||
.get_program_key()
|
||||
.unwrap(),
|
||||
Vec::new(),
|
||||
);
|
||||
let mut parameter_bytes = parameter_bytes.clone();
|
||||
{
|
||||
let mut vm = create_vm(
|
||||
|
@ -277,7 +285,10 @@ fn run_program(name: &str) -> u64 {
|
|||
}
|
||||
deserialize_parameters(
|
||||
invoke_context.transaction_context,
|
||||
invoke_context.transaction_context.get_current_instruction_context().unwrap(),
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.get_current_instruction_context()
|
||||
.unwrap(),
|
||||
parameter_bytes.as_slice(),
|
||||
&account_lengths,
|
||||
true,
|
||||
|
@ -295,7 +306,7 @@ fn process_transaction_and_record_inner(
|
|||
let signature = tx.signatures.get(0).unwrap().clone();
|
||||
let txs = vec![tx];
|
||||
let tx_batch = bank.prepare_batch_for_tests(txs);
|
||||
let (mut results, _, mut inner_instructions, _transaction_logs) = bank
|
||||
let mut results = bank
|
||||
.load_execute_and_commit_transactions(
|
||||
&tx_batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
|
@ -303,23 +314,27 @@ fn process_transaction_and_record_inner(
|
|||
true,
|
||||
false,
|
||||
&mut ExecuteTimings::default(),
|
||||
);
|
||||
)
|
||||
.0;
|
||||
let result = results
|
||||
.fee_collection_results
|
||||
.swap_remove(0)
|
||||
.and_then(|_| bank.get_signature_status(&signature).unwrap());
|
||||
(
|
||||
result,
|
||||
inner_instructions
|
||||
let inner_instructions = results
|
||||
.execution_results
|
||||
.swap_remove(0)
|
||||
.expect("cpi recording should be enabled"),
|
||||
)
|
||||
.details()
|
||||
.expect("tx should be executed")
|
||||
.clone()
|
||||
.inner_instructions
|
||||
.expect("cpi recording should be enabled");
|
||||
(result, inner_instructions)
|
||||
}
|
||||
|
||||
fn execute_transactions(
|
||||
bank: &Bank,
|
||||
txs: Vec<Transaction>,
|
||||
) -> Vec<ConfirmedTransactionWithStatusMeta> {
|
||||
) -> Vec<Result<ConfirmedTransactionWithStatusMeta, TransactionError>> {
|
||||
let batch = bank.prepare_batch_for_tests(txs.clone());
|
||||
let mut timings = ExecuteTimings::default();
|
||||
let mut mint_decimals = HashMap::new();
|
||||
|
@ -333,8 +348,6 @@ fn execute_transactions(
|
|||
post_balances,
|
||||
..
|
||||
},
|
||||
inner_instructions,
|
||||
transaction_logs,
|
||||
) = bank.load_execute_and_commit_transactions(
|
||||
&batch,
|
||||
std::usize::MAX,
|
||||
|
@ -348,30 +361,39 @@ fn execute_transactions(
|
|||
izip!(
|
||||
txs.iter(),
|
||||
execution_results.into_iter(),
|
||||
inner_instructions.into_iter(),
|
||||
pre_balances.into_iter(),
|
||||
post_balances.into_iter(),
|
||||
tx_pre_token_balances.into_iter(),
|
||||
tx_post_token_balances.into_iter(),
|
||||
transaction_logs.into_iter(),
|
||||
)
|
||||
.map(
|
||||
|(
|
||||
tx,
|
||||
(execute_result, nonce),
|
||||
inner_instructions,
|
||||
execution_result,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
pre_token_balances,
|
||||
post_token_balances,
|
||||
log_messages,
|
||||
)| {
|
||||
let lamports_per_signature = nonce
|
||||
.map(|nonce| nonce.lamports_per_signature())
|
||||
.unwrap_or_else(|| {
|
||||
bank.get_lamports_per_signature_for_blockhash(&tx.message().recent_blockhash)
|
||||
})
|
||||
.expect("lamports_per_signature must exist");
|
||||
match execution_result {
|
||||
TransactionExecutionResult::Executed(details) => {
|
||||
let TransactionExecutionDetails {
|
||||
status,
|
||||
log_messages,
|
||||
inner_instructions,
|
||||
durable_nonce_fee,
|
||||
} = details;
|
||||
|
||||
let lamports_per_signature = match durable_nonce_fee {
|
||||
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
|
||||
Some(lamports_per_signature)
|
||||
}
|
||||
Some(DurableNonceFee::Invalid) => None,
|
||||
None => bank.get_lamports_per_signature_for_blockhash(
|
||||
&tx.message().recent_blockhash,
|
||||
),
|
||||
}
|
||||
.expect("lamports_per_signature must be available");
|
||||
let fee = Bank::get_fee_for_message_with_lamports_per_signature(
|
||||
&SanitizedMessage::try_from(tx.message().clone()).unwrap(),
|
||||
lamports_per_signature,
|
||||
|
@ -390,7 +412,7 @@ fn execute_transactions(
|
|||
});
|
||||
|
||||
let tx_status_meta = TransactionStatusMeta {
|
||||
status: execute_result,
|
||||
status,
|
||||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
|
@ -401,27 +423,22 @@ fn execute_transactions(
|
|||
rewards: None,
|
||||
};
|
||||
|
||||
ConfirmedTransactionWithStatusMeta {
|
||||
Ok(ConfirmedTransactionWithStatusMeta {
|
||||
slot: bank.slot(),
|
||||
transaction: TransactionWithStatusMeta {
|
||||
transaction: tx.clone(),
|
||||
meta: Some(tx_status_meta),
|
||||
},
|
||||
block_time: None,
|
||||
})
|
||||
}
|
||||
TransactionExecutionResult::NotExecuted(err) => Err(err.clone()),
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn print_confirmed_tx(name: &str, confirmed_tx: ConfirmedTransactionWithStatusMeta) {
|
||||
let block_time = confirmed_tx.block_time;
|
||||
let tx = confirmed_tx.transaction.transaction.clone();
|
||||
let encoded = confirmed_tx.encode(UiTransactionEncoding::JsonParsed);
|
||||
println!("EXECUTE {} (slot {})", name, encoded.slot);
|
||||
println_transaction(&tx, &encoded.transaction.meta, " ", None, block_time);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "bpf_c", feature = "bpf_rust"))]
|
||||
fn test_program_bpf_sanity() {
|
||||
|
@ -2439,43 +2456,35 @@ fn test_program_upgradeable_locks() {
|
|||
execute_transactions(&bank, vec![invoke_tx, upgrade_tx])
|
||||
};
|
||||
|
||||
if false {
|
||||
println!("upgrade and invoke");
|
||||
for result in &results1 {
|
||||
print_confirmed_tx("result", result.clone());
|
||||
}
|
||||
println!("invoke and upgrade");
|
||||
for result in &results2 {
|
||||
print_confirmed_tx("result", result.clone());
|
||||
}
|
||||
}
|
||||
assert!(matches!(
|
||||
results1[0],
|
||||
Ok(ConfirmedTransactionWithStatusMeta {
|
||||
transaction: TransactionWithStatusMeta {
|
||||
meta: Some(TransactionStatusMeta { status: Ok(()), .. }),
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
));
|
||||
assert_eq!(results1[1], Err(TransactionError::AccountInUse));
|
||||
|
||||
if let Some(ref meta) = results1[0].transaction.meta {
|
||||
assert_eq!(meta.status, Ok(()));
|
||||
} else {
|
||||
panic!("no meta");
|
||||
}
|
||||
if let Some(ref meta) = results1[1].transaction.meta {
|
||||
assert_eq!(meta.status, Err(TransactionError::AccountInUse));
|
||||
} else {
|
||||
panic!("no meta");
|
||||
}
|
||||
if let Some(ref meta) = results2[0].transaction.meta {
|
||||
assert_eq!(
|
||||
meta.status,
|
||||
Err(TransactionError::InstructionError(
|
||||
assert!(matches!(
|
||||
results2[0],
|
||||
Ok(ConfirmedTransactionWithStatusMeta {
|
||||
transaction: TransactionWithStatusMeta {
|
||||
meta: Some(TransactionStatusMeta {
|
||||
status: Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::ProgramFailedToComplete
|
||||
))
|
||||
);
|
||||
} else {
|
||||
panic!("no meta");
|
||||
}
|
||||
if let Some(ref meta) = results2[1].transaction.meta {
|
||||
assert_eq!(meta.status, Err(TransactionError::AccountInUse));
|
||||
} else {
|
||||
panic!("no meta");
|
||||
}
|
||||
)),
|
||||
..
|
||||
}),
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
));
|
||||
assert_eq!(results2[1], Err(TransactionError::AccountInUse));
|
||||
}
|
||||
|
||||
#[cfg(feature = "bpf_rust")]
|
||||
|
|
|
@ -6,7 +6,9 @@ use {
|
|||
blockstore::Blockstore,
|
||||
blockstore_processor::{TransactionStatusBatch, TransactionStatusMessage},
|
||||
},
|
||||
solana_runtime::bank::{Bank, InnerInstructionsList, NonceInfo, TransactionLogMessages},
|
||||
solana_runtime::bank::{
|
||||
Bank, DurableNonceFee, TransactionExecutionDetails, TransactionExecutionResult,
|
||||
},
|
||||
solana_transaction_status::{
|
||||
extract_and_fmt_memos, InnerInstructions, Reward, TransactionStatusMeta,
|
||||
},
|
||||
|
@ -67,57 +69,45 @@ impl TransactionStatusService {
|
|||
TransactionStatusMessage::Batch(TransactionStatusBatch {
|
||||
bank,
|
||||
transactions,
|
||||
statuses,
|
||||
execution_results,
|
||||
balances,
|
||||
token_balances,
|
||||
inner_instructions,
|
||||
transaction_logs,
|
||||
rent_debits,
|
||||
}) => {
|
||||
let slot = bank.slot();
|
||||
let inner_instructions_iter: Box<
|
||||
dyn Iterator<Item = Option<InnerInstructionsList>>,
|
||||
> = if let Some(inner_instructions) = inner_instructions {
|
||||
Box::new(inner_instructions.into_iter())
|
||||
} else {
|
||||
Box::new(std::iter::repeat_with(|| None))
|
||||
};
|
||||
let transaction_logs_iter: Box<
|
||||
dyn Iterator<Item = Option<TransactionLogMessages>>,
|
||||
> = if let Some(transaction_logs) = transaction_logs {
|
||||
Box::new(transaction_logs.into_iter())
|
||||
} else {
|
||||
Box::new(std::iter::repeat_with(|| None))
|
||||
};
|
||||
for (
|
||||
transaction,
|
||||
(status, nonce),
|
||||
execution_result,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
pre_token_balances,
|
||||
post_token_balances,
|
||||
inner_instructions,
|
||||
log_messages,
|
||||
rent_debits,
|
||||
) in izip!(
|
||||
transactions,
|
||||
statuses,
|
||||
execution_results,
|
||||
balances.pre_balances,
|
||||
balances.post_balances,
|
||||
token_balances.pre_token_balances,
|
||||
token_balances.post_token_balances,
|
||||
inner_instructions_iter,
|
||||
transaction_logs_iter,
|
||||
rent_debits,
|
||||
) {
|
||||
if Bank::can_commit(&status) {
|
||||
let lamports_per_signature = nonce
|
||||
.map(|nonce| nonce.lamports_per_signature())
|
||||
.unwrap_or_else(|| {
|
||||
bank.get_lamports_per_signature_for_blockhash(
|
||||
if let TransactionExecutionResult::Executed(details) = execution_result {
|
||||
let TransactionExecutionDetails {
|
||||
status,
|
||||
log_messages,
|
||||
inner_instructions,
|
||||
durable_nonce_fee,
|
||||
} = details;
|
||||
let lamports_per_signature = match durable_nonce_fee {
|
||||
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
|
||||
Some(lamports_per_signature)
|
||||
}
|
||||
Some(DurableNonceFee::Invalid) => None,
|
||||
None => bank.get_lamports_per_signature_for_blockhash(
|
||||
transaction.message().recent_blockhash(),
|
||||
)
|
||||
})
|
||||
),
|
||||
}
|
||||
.expect("lamports_per_signature must be available");
|
||||
let fee = Bank::get_fee_for_message_with_lamports_per_signature(
|
||||
transaction.message(),
|
||||
|
@ -331,18 +321,21 @@ pub(crate) mod tests {
|
|||
let mut rent_debits = RentDebits::default();
|
||||
rent_debits.insert(&pubkey, 123, 456);
|
||||
|
||||
let transaction_result = (
|
||||
Ok(()),
|
||||
Some(
|
||||
NonceFull::from_partial(
|
||||
let transaction_result =
|
||||
TransactionExecutionResult::Executed(TransactionExecutionDetails {
|
||||
status: Ok(()),
|
||||
log_messages: None,
|
||||
inner_instructions: None,
|
||||
durable_nonce_fee: Some(DurableNonceFee::from(
|
||||
&NonceFull::from_partial(
|
||||
rollback_partial,
|
||||
&SanitizedMessage::Legacy(message),
|
||||
&[(pubkey, nonce_account)],
|
||||
&rent_debits,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
);
|
||||
)),
|
||||
});
|
||||
|
||||
let balances = TransactionBalancesSet {
|
||||
pre_balances: vec![vec![123456]],
|
||||
|
@ -374,11 +367,9 @@ pub(crate) mod tests {
|
|||
let transaction_status_batch = TransactionStatusBatch {
|
||||
bank,
|
||||
transactions: vec![transaction],
|
||||
statuses: vec![transaction_result],
|
||||
execution_results: vec![transaction_result],
|
||||
balances,
|
||||
token_balances,
|
||||
inner_instructions: None,
|
||||
transaction_logs: None,
|
||||
rent_debits: vec![rent_debits],
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
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 mut process_result = if feature_set.is_active(&tx_wide_compute_cap::id()) {
|
||||
compute_budget.process_transaction(tx, feature_set.clone())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
let durable_nonce_fee = nonce.as_ref().map(DurableNonceFee::from);
|
||||
|
||||
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,
|
||||
self.execute_loaded_transaction(
|
||||
tx,
|
||||
loaded_transaction,
|
||||
compute_budget,
|
||||
&mut timings.details,
|
||||
&*self.sysvar_cache.read().unwrap(),
|
||||
blockhash,
|
||||
lamports_per_signature,
|
||||
self.load_accounts_data_len(),
|
||||
durable_nonce_fee,
|
||||
enable_cpi_recording,
|
||||
enable_log_recording,
|
||||
execute_details_timings,
|
||||
&mut error_counters,
|
||||
)
|
||||
.map(|process_result| {
|
||||
self.store_accounts_data_len(process_result.accounts_data_len)
|
||||
});
|
||||
} else {
|
||||
// TODO: support versioned messages
|
||||
process_result = Err(TransactionError::UnsupportedVersion);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
(process_result, nonce)
|
||||
}
|
||||
})
|
||||
.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,15 +3832,18 @@ impl Bank {
|
|||
}
|
||||
}
|
||||
|
||||
if r.is_ok() {
|
||||
match execution_result.flattened_result() {
|
||||
Ok(()) => {
|
||||
tx_count += 1;
|
||||
} else {
|
||||
}
|
||||
Err(err) => {
|
||||
if *err_count == 0 {
|
||||
debug!("tx error: {:?} {:?}", r, tx);
|
||||
debug!("tx error: {:?} {:?}", err, tx);
|
||||
}
|
||||
*err_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if *err_count > 0 {
|
||||
debug!(
|
||||
"{} errors of {} txs",
|
||||
|
@ -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,8 +3918,6 @@ 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.
|
||||
|
@ -3831,18 +3925,12 @@ impl Bank {
|
|||
//...except nonce accounts, which already have their
|
||||
// post-load, fee deducted, pre-execute account state
|
||||
// stored
|
||||
if !is_nonce {
|
||||
if execution_status.is_err() && !is_nonce {
|
||||
self.withdraw(tx.message().fee_payer(), fee)?;
|
||||
}
|
||||
|
||||
fees += fee;
|
||||
Ok(())
|
||||
}
|
||||
Ok(()) => {
|
||||
fees += fee;
|
||||
Ok(())
|
||||
}
|
||||
_ => execution_result.clone(),
|
||||
}
|
||||
})
|
||||
.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();
|
||||
|
||||
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;
|
||||
rent_debits.push(mem::take(&mut loaded_transaction.rent_debits));
|
||||
mem::take(&mut loaded_transaction.rent_debits)
|
||||
} else {
|
||||
RentDebits::default()
|
||||
}
|
||||
|
||||
})
|
||||
.collect();
|
||||
self.collected_rent.fetch_add(collected_rent, Relaxed);
|
||||
rent_debits
|
||||
}
|
||||
|
@ -4601,27 +4686,15 @@ 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(
|
||||
let (mut loaded_txs, execution_results, _, tx_count, signature_count) = self
|
||||
.load_and_execute_transactions(
|
||||
batch,
|
||||
max_age,
|
||||
enable_cpi_recording,
|
||||
|
@ -4632,7 +4705,7 @@ impl Bank {
|
|||
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,18 +5556,15 @@ 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;
|
||||
}
|
||||
|
||||
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();
|
||||
let loaded_transaction = raccs.as_ref().unwrap();
|
||||
|
||||
for (_i, (pubkey, account)) in
|
||||
(0..message.account_keys_len()).zip(loaded_transaction.accounts.iter())
|
||||
{
|
||||
|
@ -5504,6 +5572,7 @@ impl Bank {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
|
||||
self.stakes_cache.stakes().staked_nodes()
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue