Count compute units even when transaction errors (#22182)

This commit is contained in:
carllin
2021-12-30 21:21:42 -05:00
committed by GitHub
parent 95dfcc546a
commit d06e6c7425
7 changed files with 339 additions and 65 deletions

View File

@ -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,
@ -520,7 +526,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());
@ -709,7 +716,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))
@ -722,8 +729,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 {
@ -743,27 +755,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
@ -1068,6 +1084,10 @@ mod tests {
ModifyOwned,
ModifyNotOwned,
ModifyReadonly,
ConsumeComputeUnits {
compute_units_consumed: u64,
desired_result: Result<(), InstructionError>,
},
}
#[test]
@ -1185,6 +1205,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);
@ -1371,12 +1402,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
@ -1390,12 +1423,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
@ -1406,15 +1441,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 {
@ -1586,4 +1639,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,
}
);
}
}
}

View File

@ -5,6 +5,18 @@ pub struct ProgramTiming {
pub accumulated_us: u64,
pub accumulated_units: u64,
pub count: u32,
pub errored_txs_compute_consumed: Vec<u64>,
}
impl ProgramTiming {
pub fn coalesce_error_timings(&mut self, current_estimated_program_cost: u64) {
for tx_error_compute_consumed in self.errored_txs_compute_consumed.drain(..) {
let compute_units_update =
std::cmp::max(current_estimated_program_cost, tx_error_compute_consumed);
self.accumulated_units = self.accumulated_units.saturating_add(compute_units_update);
self.count = self.count.saturating_add(1);
}
}
}
#[derive(Default, Debug)]
@ -46,10 +58,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,
compute_units_consumed: 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);
program_timing.count = program_timing.count.saturating_add(1);
if is_error {
program_timing
.errored_txs_compute_consumed
.push(compute_units_consumed);
} else {
program_timing.accumulated_units = program_timing
.accumulated_units
.saturating_add(compute_units_consumed);
program_timing.count = program_timing.count.saturating_add(1);
};
}
}