Count compute units even when transaction errors (#22059)
This commit is contained in:
parent
f061059e45
commit
eaa8c67bde
|
@ -25,7 +25,7 @@ use {
|
|||
TransactionExecutionResult,
|
||||
},
|
||||
bank_utils,
|
||||
cost_model::CostModel,
|
||||
cost_model::{CostModel, ExecutionCost},
|
||||
transaction_batch::TransactionBatch,
|
||||
vote_sender_types::ReplayVoteSender,
|
||||
},
|
||||
|
@ -963,14 +963,20 @@ impl BankingStage {
|
|||
let tx_costs = qos_service.compute_transaction_costs(txs.iter());
|
||||
|
||||
let transactions_qos_results =
|
||||
qos_service.select_transactions_per_cost(txs.iter(), tx_costs.iter(), bank);
|
||||
qos_service.select_transactions_per_cost(txs.iter(), tx_costs.into_iter(), bank);
|
||||
|
||||
// Only lock accounts for those transactions are selected for the block;
|
||||
// Once accounts are locked, other threads cannot encode transactions that will modify the
|
||||
// same account state
|
||||
let mut lock_time = Measure::start("lock_time");
|
||||
let batch =
|
||||
bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.into_iter());
|
||||
let batch = bank.prepare_sanitized_batch_with_results(
|
||||
txs,
|
||||
transactions_qos_results
|
||||
.into_iter()
|
||||
.map(|transaction_cost_result| {
|
||||
transaction_cost_result.map(|transaction_cost| transaction_cost.execution_cost)
|
||||
}),
|
||||
);
|
||||
lock_time.stop();
|
||||
|
||||
// retryable_txs includes AccountInUse, WouldExceedMaxBlockCostLimit
|
||||
|
@ -1070,9 +1076,9 @@ impl BankingStage {
|
|||
fn prepare_filter_for_pending_transactions(
|
||||
transactions_len: usize,
|
||||
pending_tx_indexes: &[usize],
|
||||
) -> Vec<transaction::Result<()>> {
|
||||
) -> Vec<transaction::Result<ExecutionCost>> {
|
||||
let mut mask = vec![Err(TransactionError::BlockhashNotFound); transactions_len];
|
||||
pending_tx_indexes.iter().for_each(|x| mask[*x] = Ok(()));
|
||||
pending_tx_indexes.iter().for_each(|x| mask[*x] = Ok(0));
|
||||
mask
|
||||
}
|
||||
|
||||
|
@ -1163,7 +1169,7 @@ impl BankingStage {
|
|||
|
||||
let results = bank.check_transactions(
|
||||
transactions,
|
||||
&filter,
|
||||
filter.into_iter(),
|
||||
(MAX_PROCESSING_AGE)
|
||||
.saturating_sub(max_tx_fwd_delay)
|
||||
.saturating_sub(FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET as usize),
|
||||
|
@ -1968,20 +1974,20 @@ mod tests {
|
|||
vec![
|
||||
Err(TransactionError::BlockhashNotFound),
|
||||
Err(TransactionError::BlockhashNotFound),
|
||||
Ok(()),
|
||||
Ok(0),
|
||||
Err(TransactionError::BlockhashNotFound),
|
||||
Ok(()),
|
||||
Ok(())
|
||||
Ok(0),
|
||||
Ok(0)
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
BankingStage::prepare_filter_for_pending_transactions(6, &[0, 2, 3]),
|
||||
vec![
|
||||
Ok(()),
|
||||
Ok(0),
|
||||
Err(TransactionError::BlockhashNotFound),
|
||||
Ok(()),
|
||||
Ok(()),
|
||||
Ok(0),
|
||||
Ok(0),
|
||||
Err(TransactionError::BlockhashNotFound),
|
||||
Err(TransactionError::BlockhashNotFound),
|
||||
]
|
||||
|
@ -1995,10 +2001,10 @@ mod tests {
|
|||
&[
|
||||
(Err(TransactionError::BlockhashNotFound), None),
|
||||
(Err(TransactionError::BlockhashNotFound), None),
|
||||
(Ok(()), None),
|
||||
(Ok(0), None),
|
||||
(Err(TransactionError::BlockhashNotFound), None),
|
||||
(Ok(()), None),
|
||||
(Ok(()), None),
|
||||
(Ok(0), None),
|
||||
(Ok(0), None),
|
||||
],
|
||||
&[2, 4, 5, 9, 11, 13]
|
||||
),
|
||||
|
@ -2008,12 +2014,12 @@ mod tests {
|
|||
assert_eq!(
|
||||
BankingStage::filter_valid_transaction_indexes(
|
||||
&[
|
||||
(Ok(()), None),
|
||||
(Ok(0), None),
|
||||
(Err(TransactionError::BlockhashNotFound), None),
|
||||
(Err(TransactionError::BlockhashNotFound), None),
|
||||
(Ok(()), None),
|
||||
(Ok(()), None),
|
||||
(Ok(()), None),
|
||||
(Ok(0), None),
|
||||
(Ok(0), None),
|
||||
(Ok(0), None),
|
||||
],
|
||||
&[1, 6, 7, 9, 31, 43]
|
||||
),
|
||||
|
|
|
@ -246,6 +246,7 @@ mod tests {
|
|||
ProgramTiming {
|
||||
accumulated_us,
|
||||
accumulated_units,
|
||||
current_cost_model_estimated_units: 0,
|
||||
count,
|
||||
},
|
||||
);
|
||||
|
@ -281,6 +282,7 @@ mod tests {
|
|||
ProgramTiming {
|
||||
accumulated_us,
|
||||
accumulated_units,
|
||||
current_cost_model_estimated_units: 0,
|
||||
count,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -117,18 +117,18 @@ impl QosService {
|
|||
pub fn select_transactions_per_cost<'a>(
|
||||
&self,
|
||||
transactions: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
transactions_costs: impl Iterator<Item = &'a TransactionCost>,
|
||||
transactions_costs: impl Iterator<Item = TransactionCost>,
|
||||
bank: &Arc<Bank>,
|
||||
) -> Vec<transaction::Result<()>> {
|
||||
) -> Vec<transaction::Result<TransactionCost>> {
|
||||
let mut cost_tracking_time = Measure::start("cost_tracking_time");
|
||||
let mut cost_tracker = bank.write_cost_tracker().unwrap();
|
||||
let select_results = transactions
|
||||
.zip(transactions_costs)
|
||||
.map(|(tx, cost)| match cost_tracker.try_add(tx, cost) {
|
||||
.map(|(tx, cost)| match cost_tracker.try_add(tx, &cost) {
|
||||
Ok(current_block_cost) => {
|
||||
debug!("slot {:?}, transaction {:?}, cost {:?}, fit into current block, current block cost {}", bank.slot(), tx, cost, current_block_cost);
|
||||
self.metrics.selected_txs_count.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(())
|
||||
Ok(cost)
|
||||
},
|
||||
Err(e) => {
|
||||
debug!("slot {:?}, transaction {:?}, cost {:?}, not fit into current block, '{:?}'", bank.slot(), tx, cost, e);
|
||||
|
@ -441,7 +441,8 @@ mod tests {
|
|||
bank.write_cost_tracker()
|
||||
.unwrap()
|
||||
.set_limits(cost_limit, cost_limit);
|
||||
let results = qos_service.select_transactions_per_cost(txs.iter(), txs_costs.iter(), &bank);
|
||||
let results =
|
||||
qos_service.select_transactions_per_cost(txs.iter(), txs_costs.into_iter(), &bank);
|
||||
|
||||
// verify that first transfer tx and all votes are allowed
|
||||
assert_eq!(results.len(), txs.len());
|
||||
|
|
|
@ -102,10 +102,10 @@ thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::
|
|||
.unwrap())
|
||||
);
|
||||
|
||||
fn first_err(results: &[Result<()>]) -> Result<()> {
|
||||
fn first_err<T>(results: &[Result<T>]) -> Result<()> {
|
||||
for r in results {
|
||||
if r.is_err() {
|
||||
return r.clone();
|
||||
if let Err(e) = r {
|
||||
return Err(e.clone());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -28,6 +28,12 @@ use {
|
|||
pub type ProcessInstructionWithContext =
|
||||
fn(usize, &[u8], &mut InvokeContext) -> Result<(), InstructionError>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ProcessInstructionResult {
|
||||
pub compute_units_consumed: u64,
|
||||
pub result: Result<(), InstructionError>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BuiltinProgram {
|
||||
pub program_id: Pubkey,
|
||||
|
@ -514,7 +520,8 @@ impl<'a> InvokeContext<'a> {
|
|||
&instruction_accounts,
|
||||
Some(&caller_write_privileges),
|
||||
&program_indices,
|
||||
)?;
|
||||
)
|
||||
.result?;
|
||||
|
||||
// Verify the called program has not misbehaved
|
||||
let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id());
|
||||
|
@ -695,7 +702,7 @@ impl<'a> InvokeContext<'a> {
|
|||
instruction_accounts: &[InstructionAccount],
|
||||
caller_write_privileges: Option<&[bool]>,
|
||||
program_indices: &[usize],
|
||||
) -> Result<u64, InstructionError> {
|
||||
) -> ProcessInstructionResult {
|
||||
let program_id = program_indices
|
||||
.last()
|
||||
.map(|index| *self.transaction_context.get_key_of_account_at_index(*index))
|
||||
|
@ -708,8 +715,13 @@ impl<'a> InvokeContext<'a> {
|
|||
}
|
||||
} else {
|
||||
// Verify the calling program hasn't misbehaved
|
||||
self.verify_and_update(instruction_accounts, caller_write_privileges)?;
|
||||
|
||||
let result = self.verify_and_update(instruction_accounts, caller_write_privileges);
|
||||
if result.is_err() {
|
||||
return ProcessInstructionResult {
|
||||
compute_units_consumed: 0,
|
||||
result,
|
||||
};
|
||||
}
|
||||
// Record instruction
|
||||
if let Some(instruction_recorder) = &self.instruction_recorder {
|
||||
let compiled_instruction = CompiledInstruction {
|
||||
|
@ -729,27 +741,31 @@ impl<'a> InvokeContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut compute_units_consumed = 0;
|
||||
let result = self
|
||||
.push(instruction_accounts, program_indices)
|
||||
.and_then(|_| {
|
||||
self.return_data = (program_id, Vec::new());
|
||||
let pre_remaining_units = self.compute_meter.borrow().get_remaining();
|
||||
self.process_executable_chain(instruction_data)?;
|
||||
let execution_result = self.process_executable_chain(instruction_data);
|
||||
let post_remaining_units = self.compute_meter.borrow().get_remaining();
|
||||
compute_units_consumed = pre_remaining_units.saturating_sub(post_remaining_units);
|
||||
execution_result?;
|
||||
|
||||
// Verify the called program has not misbehaved
|
||||
if is_lowest_invocation_level {
|
||||
self.verify(instruction_accounts, program_indices)?;
|
||||
self.verify(instruction_accounts, program_indices)
|
||||
} else {
|
||||
self.verify_and_update(instruction_accounts, None)?;
|
||||
self.verify_and_update(instruction_accounts, None)
|
||||
}
|
||||
|
||||
Ok(pre_remaining_units.saturating_sub(post_remaining_units))
|
||||
});
|
||||
|
||||
// Pop the invoke_stack to restore previous state
|
||||
self.pop();
|
||||
result
|
||||
ProcessInstructionResult {
|
||||
compute_units_consumed,
|
||||
result,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the instruction's program entrypoint method
|
||||
|
@ -1047,6 +1063,10 @@ mod tests {
|
|||
ModifyOwned,
|
||||
ModifyNotOwned,
|
||||
ModifyReadonly,
|
||||
ConsumeComputeUnits {
|
||||
compute_units_consumed: u64,
|
||||
desired_result: Result<(), InstructionError>,
|
||||
},
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1160,6 +1180,17 @@ mod tests {
|
|||
.try_account_ref_mut()?
|
||||
.data_as_mut_slice()[0] = 1
|
||||
}
|
||||
MockInstruction::ConsumeComputeUnits {
|
||||
compute_units_consumed,
|
||||
desired_result,
|
||||
} => {
|
||||
invoke_context
|
||||
.get_compute_meter()
|
||||
.borrow_mut()
|
||||
.consume(compute_units_consumed)
|
||||
.unwrap();
|
||||
return desired_result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(InstructionError::InvalidInstructionData);
|
||||
|
@ -1341,12 +1372,14 @@ mod tests {
|
|||
.borrow_mut()
|
||||
.data_as_mut_slice()[0] = 1;
|
||||
assert_eq!(
|
||||
invoke_context.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
&program_indices[1..],
|
||||
),
|
||||
invoke_context
|
||||
.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
&program_indices[1..],
|
||||
)
|
||||
.result,
|
||||
Err(InstructionError::ExternalAccountDataModified)
|
||||
);
|
||||
transaction_context
|
||||
|
@ -1360,12 +1393,14 @@ mod tests {
|
|||
.borrow_mut()
|
||||
.data_as_mut_slice()[0] = 1;
|
||||
assert_eq!(
|
||||
invoke_context.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
&program_indices[1..],
|
||||
),
|
||||
invoke_context
|
||||
.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
&program_indices[1..],
|
||||
)
|
||||
.result,
|
||||
Err(InstructionError::ReadonlyDataModified)
|
||||
);
|
||||
transaction_context
|
||||
|
@ -1376,15 +1411,33 @@ mod tests {
|
|||
invoke_context.pop();
|
||||
|
||||
let cases = vec![
|
||||
(MockInstruction::NoopSuccess, Ok(0)),
|
||||
(
|
||||
MockInstruction::NoopSuccess,
|
||||
ProcessInstructionResult {
|
||||
result: Ok(()),
|
||||
compute_units_consumed: 0,
|
||||
},
|
||||
),
|
||||
(
|
||||
MockInstruction::NoopFail,
|
||||
Err(InstructionError::GenericError),
|
||||
ProcessInstructionResult {
|
||||
result: Err(InstructionError::GenericError),
|
||||
compute_units_consumed: 0,
|
||||
},
|
||||
),
|
||||
(
|
||||
MockInstruction::ModifyOwned,
|
||||
ProcessInstructionResult {
|
||||
result: Ok(()),
|
||||
compute_units_consumed: 0,
|
||||
},
|
||||
),
|
||||
(MockInstruction::ModifyOwned, Ok(0)),
|
||||
(
|
||||
MockInstruction::ModifyNotOwned,
|
||||
Err(InstructionError::ExternalAccountDataModified),
|
||||
ProcessInstructionResult {
|
||||
result: Err(InstructionError::ExternalAccountDataModified),
|
||||
compute_units_consumed: 0,
|
||||
},
|
||||
),
|
||||
];
|
||||
for case in cases {
|
||||
|
@ -1555,4 +1608,83 @@ mod tests {
|
|||
);
|
||||
invoke_context.pop();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_instruction_compute_budget() {
|
||||
let caller_program_id = solana_sdk::pubkey::new_rand();
|
||||
let callee_program_id = solana_sdk::pubkey::new_rand();
|
||||
let builtin_programs = &[BuiltinProgram {
|
||||
program_id: callee_program_id,
|
||||
process_instruction: mock_process_instruction,
|
||||
}];
|
||||
|
||||
let owned_account = AccountSharedData::new(42, 1, &callee_program_id);
|
||||
let not_owned_account = AccountSharedData::new(84, 1, &solana_sdk::pubkey::new_rand());
|
||||
let readonly_account = AccountSharedData::new(168, 1, &solana_sdk::pubkey::new_rand());
|
||||
let loader_account = AccountSharedData::new(0, 0, &native_loader::id());
|
||||
let mut program_account = AccountSharedData::new(1, 0, &native_loader::id());
|
||||
program_account.set_executable(true);
|
||||
|
||||
let accounts = vec![
|
||||
(solana_sdk::pubkey::new_rand(), owned_account),
|
||||
(solana_sdk::pubkey::new_rand(), not_owned_account),
|
||||
(solana_sdk::pubkey::new_rand(), readonly_account),
|
||||
(caller_program_id, loader_account),
|
||||
(callee_program_id, program_account),
|
||||
];
|
||||
let program_indices = [3, 4];
|
||||
|
||||
let metas = vec![
|
||||
AccountMeta::new(accounts[0].0, false),
|
||||
AccountMeta::new(accounts[1].0, false),
|
||||
AccountMeta::new_readonly(accounts[2].0, false),
|
||||
];
|
||||
let instruction_accounts = metas
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(account_index, account_meta)| InstructionAccount {
|
||||
index: account_index,
|
||||
is_signer: account_meta.is_signer,
|
||||
is_writable: account_meta.is_writable,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let transaction_context = TransactionContext::new(accounts, 1);
|
||||
let mut invoke_context = InvokeContext::new_mock(&transaction_context, builtin_programs);
|
||||
let compute_units_consumed = 10;
|
||||
let desired_results = vec![Ok(()), Err(InstructionError::GenericError)];
|
||||
|
||||
for desired_result in desired_results {
|
||||
let instruction = Instruction::new_with_bincode(
|
||||
callee_program_id,
|
||||
&MockInstruction::ConsumeComputeUnits {
|
||||
compute_units_consumed,
|
||||
desired_result: desired_result.clone(),
|
||||
},
|
||||
metas.clone(),
|
||||
);
|
||||
invoke_context
|
||||
.push(&instruction_accounts, &program_indices[..1])
|
||||
.unwrap();
|
||||
|
||||
let result = invoke_context.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
&program_indices[1..],
|
||||
);
|
||||
|
||||
// Because the instruction had compute cost > 0, then regardless of the execution result,
|
||||
// the number of compute units consumed should be a non-default which is something greater
|
||||
// than zero.
|
||||
assert!(result.compute_units_consumed > 0);
|
||||
assert_eq!(
|
||||
result,
|
||||
ProcessInstructionResult {
|
||||
compute_units_consumed,
|
||||
result: desired_result,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use {solana_sdk::pubkey::Pubkey, std::collections::HashMap};
|
|||
pub struct ProgramTiming {
|
||||
pub accumulated_us: u64,
|
||||
pub accumulated_units: u64,
|
||||
pub current_cost_model_estimated_units: u64,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
|
@ -46,10 +47,24 @@ impl ExecuteDetailsTimings {
|
|||
program_timing.count = program_timing.count.saturating_add(other.count);
|
||||
}
|
||||
}
|
||||
pub fn accumulate_program(&mut self, program_id: &Pubkey, us: u64, units: u64) {
|
||||
pub fn accumulate_program(
|
||||
&mut self,
|
||||
program_id: &Pubkey,
|
||||
us: u64,
|
||||
actual_compute_units_consumed: u64,
|
||||
estimated_execution_cost: u64,
|
||||
is_error: bool,
|
||||
) {
|
||||
let program_timing = self.per_program_timings.entry(*program_id).or_default();
|
||||
program_timing.accumulated_us = program_timing.accumulated_us.saturating_add(us);
|
||||
program_timing.accumulated_units = program_timing.accumulated_units.saturating_add(units);
|
||||
let compute_units_update = if is_error {
|
||||
std::cmp::max(actual_compute_units_consumed, estimated_execution_cost)
|
||||
} else {
|
||||
actual_compute_units_consumed
|
||||
};
|
||||
program_timing.accumulated_units = program_timing
|
||||
.accumulated_units
|
||||
.saturating_add(compute_units_update);
|
||||
program_timing.count = program_timing.count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -283,6 +283,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
|
|||
Some(&caller_write_privileges),
|
||||
&program_indices,
|
||||
)
|
||||
.result
|
||||
.map_err(|err| ProgramError::try_from(err).unwrap_or_else(|err| panic!("{}", err)))?;
|
||||
|
||||
// Copy writeable account modifications back into the caller's AccountInfos
|
||||
|
|
|
@ -2395,6 +2395,7 @@ fn call<'a, 'b: 'a>(
|
|||
Some(&caller_write_privileges),
|
||||
&program_indices,
|
||||
)
|
||||
.result
|
||||
.map_err(SyscallError::InstructionError)?;
|
||||
|
||||
// Copy results back to caller
|
||||
|
|
|
@ -13,6 +13,7 @@ use {
|
|||
TransactionExecutionResult,
|
||||
},
|
||||
blockhash_queue::BlockhashQueue,
|
||||
cost_model::ExecutionCost,
|
||||
rent_collector::RentCollector,
|
||||
system_instruction_processor::{get_system_account_kind, SystemAccountKind},
|
||||
},
|
||||
|
@ -114,6 +115,7 @@ pub struct LoadedTransaction {
|
|||
pub program_indices: TransactionProgramIndices,
|
||||
pub rent: TransactionRent,
|
||||
pub rent_debits: RentDebits,
|
||||
pub estimated_execution_cost: ExecutionCost,
|
||||
}
|
||||
|
||||
pub type TransactionLoadResult = (Result<LoadedTransaction>, Option<NonceFull>);
|
||||
|
@ -231,6 +233,7 @@ impl Accounts {
|
|||
error_counters: &mut ErrorCounters,
|
||||
rent_collector: &RentCollector,
|
||||
feature_set: &FeatureSet,
|
||||
estimated_execution_cost: ExecutionCost,
|
||||
) -> Result<LoadedTransaction> {
|
||||
// Copy all the accounts
|
||||
let message = tx.message();
|
||||
|
@ -374,6 +377,7 @@ impl Accounts {
|
|||
program_indices,
|
||||
rent: tx_rent,
|
||||
rent_debits,
|
||||
estimated_execution_cost,
|
||||
})
|
||||
} else {
|
||||
error_counters.account_not_found += 1;
|
||||
|
@ -467,7 +471,7 @@ impl Accounts {
|
|||
txs.iter()
|
||||
.zip(lock_results)
|
||||
.map(|etx| match etx {
|
||||
(tx, (Ok(()), nonce)) => {
|
||||
(tx, (Ok(execution_cost), nonce)) => {
|
||||
let lamports_per_signature = nonce
|
||||
.as_ref()
|
||||
.map(|nonce| nonce.lamports_per_signature())
|
||||
|
@ -487,6 +491,7 @@ impl Accounts {
|
|||
error_counters,
|
||||
rent_collector,
|
||||
feature_set,
|
||||
execution_cost,
|
||||
) {
|
||||
Ok(loaded_transaction) => loaded_transaction,
|
||||
Err(e) => return (Err(e), None),
|
||||
|
@ -978,11 +983,14 @@ impl Accounts {
|
|||
pub fn lock_accounts<'a>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
) -> Vec<Result<()>> {
|
||||
) -> Vec<Result<ExecutionCost>> {
|
||||
let keys: Vec<_> = txs.map(|tx| tx.get_account_locks()).collect();
|
||||
let account_locks = &mut self.account_locks.lock().unwrap();
|
||||
keys.into_iter()
|
||||
.map(|keys| self.lock_account(account_locks, keys.writable, keys.readonly))
|
||||
.map(|keys| {
|
||||
self.lock_account(account_locks, keys.writable, keys.readonly)
|
||||
.map(|_| 0)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -991,12 +999,12 @@ impl Accounts {
|
|||
pub fn lock_accounts_with_results<'a>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
results: impl Iterator<Item = Result<()>>,
|
||||
) -> Vec<Result<()>> {
|
||||
results: impl Iterator<Item = Result<ExecutionCost>>,
|
||||
) -> Vec<Result<ExecutionCost>> {
|
||||
let key_results: Vec<_> = txs
|
||||
.zip(results)
|
||||
.map(|(tx, result)| match result {
|
||||
Ok(()) => Ok(tx.get_account_locks()),
|
||||
Ok(execution_cost) => Ok((tx.get_account_locks(), execution_cost)),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.collect();
|
||||
|
@ -1004,7 +1012,9 @@ impl Accounts {
|
|||
key_results
|
||||
.into_iter()
|
||||
.map(|key_result| match key_result {
|
||||
Ok(keys) => self.lock_account(account_locks, keys.writable, keys.readonly),
|
||||
Ok((keys, execution_cost)) => self
|
||||
.lock_account(account_locks, keys.writable, keys.readonly)
|
||||
.map(|_| execution_cost),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.collect()
|
||||
|
@ -1015,7 +1025,7 @@ impl Accounts {
|
|||
pub fn unlock_accounts<'a>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
results: &[Result<()>],
|
||||
results: &[Result<ExecutionCost>],
|
||||
) {
|
||||
let keys: Vec<_> = txs
|
||||
.zip(results)
|
||||
|
@ -1300,7 +1310,7 @@ mod tests {
|
|||
accounts.load_accounts(
|
||||
&ancestors,
|
||||
&[sanitized_tx],
|
||||
vec![(Ok(()), None)],
|
||||
vec![(Ok(0), None)],
|
||||
&hash_queue,
|
||||
error_counters,
|
||||
rent_collector,
|
||||
|
@ -2448,9 +2458,9 @@ mod tests {
|
|||
let txs = vec![tx0, tx1, tx2];
|
||||
|
||||
let qos_results = vec![
|
||||
Ok(()),
|
||||
Ok(0),
|
||||
Err(TransactionError::WouldExceedMaxBlockCostLimit),
|
||||
Ok(()),
|
||||
Ok(0),
|
||||
];
|
||||
|
||||
let results = accounts.lock_accounts_with_results(txs.iter(), qos_results.into_iter());
|
||||
|
@ -2543,6 +2553,7 @@ mod tests {
|
|||
program_indices: vec![],
|
||||
rent: 0,
|
||||
rent_debits: RentDebits::default(),
|
||||
estimated_execution_cost: 0,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
@ -2553,6 +2564,7 @@ mod tests {
|
|||
program_indices: vec![],
|
||||
rent: 0,
|
||||
rent_debits: RentDebits::default(),
|
||||
estimated_execution_cost: 0,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
@ -2646,7 +2658,7 @@ mod tests {
|
|||
accounts.load_accounts(
|
||||
&ancestors,
|
||||
&[tx],
|
||||
vec![(Ok(()), None)],
|
||||
vec![(Ok(0), None)],
|
||||
&hash_queue,
|
||||
&mut error_counters,
|
||||
&rent_collector,
|
||||
|
@ -2982,6 +2994,7 @@ mod tests {
|
|||
program_indices: vec![],
|
||||
rent: 0,
|
||||
rent_debits: RentDebits::default(),
|
||||
estimated_execution_cost: 0,
|
||||
}),
|
||||
nonce.clone(),
|
||||
);
|
||||
|
@ -3092,6 +3105,7 @@ mod tests {
|
|||
program_indices: vec![],
|
||||
rent: 0,
|
||||
rent_debits: RentDebits::default(),
|
||||
estimated_execution_cost: 0,
|
||||
}),
|
||||
nonce.clone(),
|
||||
);
|
||||
|
|
|
@ -47,6 +47,7 @@ use {
|
|||
ancestors::{Ancestors, AncestorsForSerialization},
|
||||
blockhash_queue::BlockhashQueue,
|
||||
builtins::{self, ActivationType, Builtin, Builtins},
|
||||
cost_model::ExecutionCost,
|
||||
cost_tracker::CostTracker,
|
||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||
inline_spl_token,
|
||||
|
@ -503,7 +504,7 @@ impl StatusCacheRc {
|
|||
}
|
||||
}
|
||||
|
||||
pub type TransactionCheckResult = (Result<()>, Option<NoncePartial>);
|
||||
pub type TransactionCheckResult<'a> = (Result<ExecutionCost>, Option<NoncePartial>);
|
||||
pub type TransactionExecutionResult = (Result<()>, Option<NonceFull>);
|
||||
pub struct TransactionResults {
|
||||
pub fee_collection_results: Vec<Result<()>>,
|
||||
|
@ -3107,7 +3108,7 @@ impl Bank {
|
|||
pub fn prepare_sanitized_batch_with_results<'a, 'b>(
|
||||
&'a self,
|
||||
transactions: &'b [SanitizedTransaction],
|
||||
transaction_results: impl Iterator<Item = Result<()>>,
|
||||
transaction_results: impl Iterator<Item = Result<ExecutionCost>>,
|
||||
) -> TransactionBatch<'a, 'b> {
|
||||
// this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit
|
||||
let lock_results = self
|
||||
|
@ -3122,7 +3123,7 @@ impl Bank {
|
|||
&'a self,
|
||||
transaction: SanitizedTransaction,
|
||||
) -> TransactionBatch<'a, '_> {
|
||||
let mut batch = TransactionBatch::new(vec![Ok(())], self, Cow::Owned(vec![transaction]));
|
||||
let mut batch = TransactionBatch::new(vec![Ok(0)], self, Cow::Owned(vec![transaction]));
|
||||
batch.needs_unlock = false;
|
||||
batch
|
||||
}
|
||||
|
@ -3218,23 +3219,29 @@ impl Bank {
|
|||
self.rc.accounts.accounts_db.set_shrink_paths(paths);
|
||||
}
|
||||
|
||||
fn check_age<'a>(
|
||||
fn check_age<'a, T>(
|
||||
&self,
|
||||
txs: impl Iterator<Item = &'a SanitizedTransaction>,
|
||||
lock_results: &[Result<()>],
|
||||
lock_results: impl Iterator<Item = T>,
|
||||
max_age: usize,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Vec<TransactionCheckResult> {
|
||||
) -> Vec<TransactionCheckResult>
|
||||
where
|
||||
T: std::borrow::Borrow<Result<ExecutionCost>>,
|
||||
{
|
||||
let hash_queue = self.blockhash_queue.read().unwrap();
|
||||
txs.zip(lock_results)
|
||||
.map(|(tx, lock_res)| match lock_res {
|
||||
Ok(()) => {
|
||||
.map(|(tx, lock_res)| match lock_res.borrow() {
|
||||
Ok(execution_cost) => {
|
||||
let recent_blockhash = tx.message().recent_blockhash();
|
||||
let hash_age = hash_queue.check_hash_age(recent_blockhash, max_age);
|
||||
if hash_age == Some(true) {
|
||||
(Ok(()), None)
|
||||
(Ok(*execution_cost), None)
|
||||
} else if let Some((address, account)) = self.check_transaction_for_nonce(tx) {
|
||||
(Ok(()), Some(NoncePartial::new(address, account)))
|
||||
(
|
||||
Ok(*execution_cost),
|
||||
Some(NoncePartial::new(address, account)),
|
||||
)
|
||||
} else if hash_age == Some(false) {
|
||||
error_counters.blockhash_too_old += 1;
|
||||
(Err(TransactionError::BlockhashNotFound), None)
|
||||
|
@ -3304,13 +3311,16 @@ impl Bank {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn check_transactions(
|
||||
pub fn check_transactions<T>(
|
||||
&self,
|
||||
sanitized_txs: &[SanitizedTransaction],
|
||||
lock_results: &[Result<()>],
|
||||
lock_results: impl Iterator<Item = T>,
|
||||
max_age: usize,
|
||||
error_counters: &mut ErrorCounters,
|
||||
) -> Vec<TransactionCheckResult> {
|
||||
) -> Vec<TransactionCheckResult>
|
||||
where
|
||||
T: std::borrow::Borrow<Result<ExecutionCost>>,
|
||||
{
|
||||
let age_results =
|
||||
self.check_age(sanitized_txs.iter(), lock_results, max_age, error_counters);
|
||||
self.check_status_cache(sanitized_txs, age_results, error_counters)
|
||||
|
@ -3506,7 +3516,7 @@ impl Bank {
|
|||
let mut check_time = Measure::start("check_transactions");
|
||||
let check_results = self.check_transactions(
|
||||
sanitized_txs,
|
||||
batch.lock_results(),
|
||||
batch.lock_results().iter(),
|
||||
max_age,
|
||||
&mut error_counters,
|
||||
);
|
||||
|
@ -3588,6 +3598,7 @@ impl Bank {
|
|||
&self.builtin_programs.vec,
|
||||
legacy_message,
|
||||
&loaded_transaction.program_indices,
|
||||
loaded_transaction.estimated_execution_cost,
|
||||
&transaction_context,
|
||||
self.rent_collector.rent,
|
||||
log_collector.clone(),
|
||||
|
|
|
@ -18,6 +18,7 @@ use {
|
|||
};
|
||||
|
||||
const MAX_WRITABLE_ACCOUNTS: usize = 256;
|
||||
pub type ExecutionCost = u64;
|
||||
|
||||
// costs are stored in number of 'compute unit's
|
||||
#[derive(Debug)]
|
||||
|
@ -26,7 +27,7 @@ pub struct TransactionCost {
|
|||
pub signature_cost: u64,
|
||||
pub write_lock_cost: u64,
|
||||
pub data_bytes_cost: u64,
|
||||
pub execution_cost: u64,
|
||||
pub execution_cost: ExecutionCost,
|
||||
// `cost_weight` is a multiplier could be applied to transaction cost,
|
||||
// if set to zero allows the transaction to bypass cost limit check.
|
||||
pub cost_weight: u32,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use {
|
||||
crate::cost_model::ExecutionCost,
|
||||
serde::{Deserialize, Serialize},
|
||||
solana_measure::measure::Measure,
|
||||
solana_program_runtime::{
|
||||
instruction_recorder::InstructionRecorder,
|
||||
invoke_context::{BuiltinProgram, Executors, InvokeContext},
|
||||
invoke_context::{BuiltinProgram, Executors, InvokeContext, ProcessInstructionResult},
|
||||
log_collector::LogCollector,
|
||||
timings::ExecuteDetailsTimings,
|
||||
},
|
||||
|
@ -53,6 +54,7 @@ impl MessageProcessor {
|
|||
builtin_programs: &[BuiltinProgram],
|
||||
message: &Message,
|
||||
program_indices: &[Vec<usize>],
|
||||
estimated_execution_cost: ExecutionCost,
|
||||
transaction_context: &TransactionContext,
|
||||
rent: Rent,
|
||||
log_collector: Option<Rc<RefCell<LogCollector>>>,
|
||||
|
@ -127,21 +129,26 @@ impl MessageProcessor {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut time = Measure::start("execute_instruction");
|
||||
let compute_meter_consumption = invoke_context
|
||||
.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
program_indices,
|
||||
)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
let ProcessInstructionResult {
|
||||
compute_units_consumed,
|
||||
result,
|
||||
} = invoke_context.process_instruction(
|
||||
&instruction.data,
|
||||
&instruction_accounts,
|
||||
None,
|
||||
program_indices,
|
||||
);
|
||||
time.stop();
|
||||
timings.accumulate_program(
|
||||
instruction.program_id(&message.account_keys),
|
||||
time.as_us(),
|
||||
compute_meter_consumption,
|
||||
compute_units_consumed,
|
||||
estimated_execution_cost,
|
||||
result.is_err(),
|
||||
);
|
||||
timings.accumulate(&invoke_context.timings);
|
||||
result
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
}
|
||||
Ok(ProcessedMessageInfo {
|
||||
accounts_data_len: invoke_context.get_accounts_data_meter().current(),
|
||||
|
@ -258,6 +265,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&program_indices,
|
||||
0,
|
||||
&transaction_context,
|
||||
rent_collector.rent,
|
||||
None,
|
||||
|
@ -299,6 +307,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&program_indices,
|
||||
0,
|
||||
&transaction_context,
|
||||
rent_collector.rent,
|
||||
None,
|
||||
|
@ -332,6 +341,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&program_indices,
|
||||
0,
|
||||
&transaction_context,
|
||||
rent_collector.rent,
|
||||
None,
|
||||
|
@ -476,6 +486,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&program_indices,
|
||||
0,
|
||||
&transaction_context,
|
||||
rent_collector.rent,
|
||||
None,
|
||||
|
@ -510,6 +521,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&program_indices,
|
||||
0,
|
||||
&transaction_context,
|
||||
rent_collector.rent,
|
||||
None,
|
||||
|
@ -541,6 +553,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&program_indices,
|
||||
0,
|
||||
&transaction_context,
|
||||
rent_collector.rent,
|
||||
None,
|
||||
|
@ -614,6 +627,7 @@ mod tests {
|
|||
builtin_programs,
|
||||
&message,
|
||||
&[vec![0], vec![1]],
|
||||
0,
|
||||
&transaction_context,
|
||||
RentCollector::default().rent,
|
||||
None,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use {
|
||||
crate::bank::Bank,
|
||||
crate::{bank::Bank, cost_model::ExecutionCost},
|
||||
solana_sdk::transaction::{Result, SanitizedTransaction},
|
||||
std::borrow::Cow,
|
||||
};
|
||||
|
||||
// Represents the results of trying to lock a set of accounts
|
||||
pub struct TransactionBatch<'a, 'b> {
|
||||
lock_results: Vec<Result<()>>,
|
||||
lock_results: Vec<Result<ExecutionCost>>,
|
||||
bank: &'a Bank,
|
||||
sanitized_txs: Cow<'b, [SanitizedTransaction]>,
|
||||
pub(crate) needs_unlock: bool,
|
||||
|
@ -14,7 +14,7 @@ pub struct TransactionBatch<'a, 'b> {
|
|||
|
||||
impl<'a, 'b> TransactionBatch<'a, 'b> {
|
||||
pub fn new(
|
||||
lock_results: Vec<Result<()>>,
|
||||
lock_results: Vec<Result<ExecutionCost>>,
|
||||
bank: &'a Bank,
|
||||
sanitized_txs: Cow<'b, [SanitizedTransaction]>,
|
||||
) -> Self {
|
||||
|
@ -27,7 +27,7 @@ impl<'a, 'b> TransactionBatch<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn lock_results(&self) -> &Vec<Result<()>> {
|
||||
pub fn lock_results(&self) -> &Vec<Result<ExecutionCost>> {
|
||||
&self.lock_results
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue