Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blockifier): keep revert error structure in TransactionExecutionInfo #1491

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/blockifier/src/concurrency/worker_logic_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ fn test_deploy_before_declare(
let execution_output = worker_executor.execution_outputs[1].lock().unwrap();
let tx_execution_info = execution_output.as_ref().unwrap().result.as_ref().unwrap();
assert!(tx_execution_info.is_reverted());
assert!(tx_execution_info.revert_error.clone().unwrap().contains("not declared."));
assert!(tx_execution_info.revert_error.clone().unwrap().to_string().contains("not declared."));
drop(execution_output);

// Creates 2 active tasks.
Expand Down
71 changes: 58 additions & 13 deletions crates/blockifier/src/execution/stack_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub const TRACE_LENGTH_CAP: usize = 15000;
pub const TRACE_EXTRA_CHARS_SLACK: usize = 100;

#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum PreambleType {
CallContract,
LibraryCall,
Expand All @@ -40,7 +41,9 @@ impl PreambleType {
}
}

#[cfg_attr(any(test, feature = "testing"), derive(Clone))]
#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct EntryPointErrorFrame {
pub depth: usize,
pub preamble_type: PreambleType,
Expand Down Expand Up @@ -72,7 +75,9 @@ impl From<&EntryPointErrorFrame> for String {
}
}

#[cfg_attr(any(test, feature = "testing"), derive(Clone))]
#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq)]
pub struct VmExceptionFrame {
pub pc: Relocatable,
pub error_attr_value: Option<String>,
Expand All @@ -95,8 +100,9 @@ impl From<&VmExceptionFrame> for String {
}
}

#[cfg_attr(any(test, feature = "testing"), derive(Clone))]
#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(derive_more::From)]
#[derive(Debug, PartialEq, derive_more::From)]
pub enum ErrorStackSegment {
EntryPoint(EntryPointErrorFrame),
Cairo1RevertSummary(Cairo1RevertSummary),
Expand All @@ -118,24 +124,52 @@ impl From<&ErrorStackSegment> for String {
}

#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Default)]
#[derive(Clone, Debug, Default, PartialEq)]
pub enum ErrorStackHeader {
Constructor,
Execution,
Validation,
#[default]
None,
}

impl Display for ErrorStackHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Constructor => "Contract constructor execution has failed:\n",
Self::Execution => "Transaction execution has failed:\n",
Self::Validation => "Transaction validation has failed:\n",
Self::None => "",
}
)
}
}

#[cfg_attr(any(test, feature = "testing"), derive(Clone))]
#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Default, PartialEq)]
pub struct ErrorStack {
pub header: ErrorStackHeader,
pub stack: Vec<ErrorStackSegment>,
}

impl From<ErrorStack> for String {
fn from(value: ErrorStack) -> Self {
let error_stack_str = value.stack.iter().map(String::from).join("\n");
impl Display for ErrorStack {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let error_stack_str = self.stack.iter().map(String::from).join("\n");

// When the trace string is too long, trim it in a way that keeps both the beginning and
// end.
if error_stack_str.len() > TRACE_LENGTH_CAP + TRACE_EXTRA_CHARS_SLACK {
let final_str = if error_stack_str.len() > TRACE_LENGTH_CAP + TRACE_EXTRA_CHARS_SLACK {
error_stack_str[..(TRACE_LENGTH_CAP / 2)].to_string()
+ "\n\n...\n\n"
+ &error_stack_str[(error_stack_str.len() - TRACE_LENGTH_CAP / 2)..]
} else {
error_stack_str
}
};
write!(f, "{}{}", self.header, final_str)
}
}

Expand All @@ -144,8 +178,9 @@ impl ErrorStack {
self.stack.push(frame);
}
}

#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Cairo1RevertFrame {
pub contract_address: ContractAddress,
pub class_hash: Option<ClassHash>,
Expand Down Expand Up @@ -188,7 +223,7 @@ impl Display for Cairo1RevertFrame {
}

#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Cairo1RevertHeader {
Execution,
Validation,
Expand All @@ -208,7 +243,7 @@ impl Display for Cairo1RevertHeader {
}

#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Cairo1RevertSummary {
pub header: Cairo1RevertHeader,
pub stack: Vec<Cairo1RevertFrame>,
Expand Down Expand Up @@ -372,13 +407,21 @@ pub fn gen_tx_execution_error_trace(error: &TransactionExecutionError) -> ErrorS
class_hash,
storage_address,
selector,
}
| TransactionExecutionError::ValidateTransactionError {
} => gen_error_trace_from_entry_point_error(
ErrorStackHeader::Execution,
error,
storage_address,
class_hash,
Some(selector),
PreambleType::CallContract,
),
TransactionExecutionError::ValidateTransactionError {
error,
class_hash,
storage_address,
selector,
} => gen_error_trace_from_entry_point_error(
ErrorStackHeader::Validation,
error,
storage_address,
class_hash,
Expand All @@ -393,6 +436,7 @@ pub fn gen_tx_execution_error_trace(error: &TransactionExecutionError) -> ErrorS
constructor_selector,
},
) => gen_error_trace_from_entry_point_error(
ErrorStackHeader::Constructor,
error,
storage_address,
class_hash,
Expand All @@ -415,13 +459,14 @@ pub fn gen_tx_execution_error_trace(error: &TransactionExecutionError) -> ErrorS

/// Generate error stack from top-level entry point execution error.
fn gen_error_trace_from_entry_point_error(
header: ErrorStackHeader,
error: &EntryPointExecutionError,
storage_address: &ContractAddress,
class_hash: &ClassHash,
entry_point_selector: Option<&EntryPointSelector>,
preamble_type: PreambleType,
) -> ErrorStack {
let mut error_stack: ErrorStack = ErrorStack::default();
let mut error_stack = ErrorStack { header, ..Default::default() };
let depth = 0;
error_stack.push(
EntryPointErrorFrame {
Expand Down
3 changes: 2 additions & 1 deletion crates/blockifier/src/fee/fee_checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use crate::state::state_api::StateReader;
use crate::transaction::errors::TransactionExecutionError;
use crate::transaction::objects::{FeeType, TransactionExecutionResult, TransactionInfo};

#[derive(Clone, Copy, Debug, Error)]
#[cfg_attr(feature = "transaction_serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, Error, PartialEq)]
pub enum FeeCheckError {
#[error(
"Insufficient max {resource}: max amount: {max_amount}, actual used: {actual_amount}."
Expand Down
15 changes: 10 additions & 5 deletions crates/blockifier/src/transaction/account_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ use crate::context::{BlockContext, TransactionContext};
use crate::execution::call_info::CallInfo;
use crate::execution::contract_class::RunnableContractClass;
use crate::execution::entry_point::{CallEntryPoint, CallType, EntryPointExecutionContext};
use crate::execution::stack_trace::{extract_trailing_cairo1_revert_trace, Cairo1RevertHeader};
use crate::execution::stack_trace::{
extract_trailing_cairo1_revert_trace,
gen_tx_execution_error_trace,
Cairo1RevertHeader,
};
use crate::fee::fee_checks::{FeeCheckReportFields, PostExecutionReport};
use crate::fee::fee_utils::{
get_fee_by_gas_vector,
Expand All @@ -52,6 +56,7 @@ use crate::transaction::errors::{
use crate::transaction::objects::{
DeprecatedTransactionInfo,
HasRelatedFeeType,
RevertError,
TransactionExecutionInfo,
TransactionExecutionResult,
TransactionInfo,
Expand Down Expand Up @@ -704,7 +709,7 @@ impl AccountTransaction {
execution_state.abort();
Ok(ValidateExecuteCallInfo::new_reverted(
validate_call_info,
post_execution_error.to_string(),
post_execution_error.into(),
TransactionReceipt {
fee: post_execution_report.recommended_fee(),
..revert_cost
Expand All @@ -729,7 +734,7 @@ impl AccountTransaction {
PostExecutionReport::new(state, &tx_context, &revert_cost, charge_fee)?;
Ok(ValidateExecuteCallInfo::new_reverted(
validate_call_info,
execution_error.to_string(),
gen_tx_execution_error_trace(&execution_error).into(),
TransactionReceipt {
fee: post_execution_report.recommended_fee(),
..revert_cost
Expand Down Expand Up @@ -852,7 +857,7 @@ impl TransactionInfoCreator for AccountTransaction {
struct ValidateExecuteCallInfo {
validate_call_info: Option<CallInfo>,
execute_call_info: Option<CallInfo>,
revert_error: Option<String>,
revert_error: Option<RevertError>,
final_cost: TransactionReceipt,
}

Expand All @@ -867,7 +872,7 @@ impl ValidateExecuteCallInfo {

pub fn new_reverted(
validate_call_info: Option<CallInfo>,
revert_error: String,
revert_error: RevertError,
final_cost: TransactionReceipt,
) -> Self {
Self {
Expand Down
23 changes: 19 additions & 4 deletions crates/blockifier/src/transaction/account_transactions_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ fn test_invoke_tx_from_non_deployed_account(
match tx_result {
Ok(info) => {
// Make sure the error is because the account wasn't deployed.
assert!(info.revert_error.is_some_and(|err_str| err_str.contains(expected_error)));
assert!(info.revert_error.unwrap().to_string().contains(expected_error));
}
Err(err) => {
// Make sure the error is because the account wasn't deployed.
Expand Down Expand Up @@ -422,6 +422,7 @@ fn test_infinite_recursion(
tx_execution_info
.revert_error
.unwrap()
.to_string()
.contains("RunResources has no remaining steps.")
);
}
Expand Down Expand Up @@ -630,7 +631,14 @@ fn test_recursion_depth_exceeded(
InvokeTxArgs { calldata, nonce: nonce_manager.next(account_address), ..invoke_args };
let tx_execution_info = run_invoke_tx(&mut state, &block_context, invoke_args);

assert!(tx_execution_info.unwrap().revert_error.unwrap().contains("recursion depth exceeded"));
assert!(
tx_execution_info
.unwrap()
.revert_error
.unwrap()
.to_string()
.contains("recursion depth exceeded")
);
}

#[rstest]
Expand Down Expand Up @@ -1232,6 +1240,7 @@ fn test_insufficient_max_fee_reverts(
tx_execution_info2
.revert_error
.unwrap()
.to_string()
.contains(&format!("Insufficient max {overdraft_resource}"))
);

Expand All @@ -1252,7 +1261,7 @@ fn test_insufficient_max_fee_reverts(
assert!(tx_execution_info3.is_reverted());
assert_eq!(tx_execution_info3.receipt.da_gas, tx_execution_info1.receipt.da_gas);
assert_eq!(tx_execution_info3.receipt.fee, tx_execution_info1.receipt.fee);
assert!(tx_execution_info3.revert_error.unwrap().contains("no remaining steps"));
assert!(tx_execution_info3.revert_error.unwrap().to_string().contains("no remaining steps"));
}

#[rstest]
Expand Down Expand Up @@ -1742,7 +1751,13 @@ fn test_revert_in_execute(
.unwrap();

assert!(tx_execution_info.is_reverted());
assert!(tx_execution_info.revert_error.unwrap().contains("Failed to deserialize param #1"));
assert!(
tx_execution_info
.revert_error
.unwrap()
.to_string()
.contains("Failed to deserialize param #1")
);
}

#[rstest]
Expand Down
15 changes: 3 additions & 12 deletions crates/blockifier/src/transaction/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,11 @@ pub enum TransactionExecutionError {
version {cairo_version:?}.", **declare_version
)]
ContractClassVersionMismatch { declare_version: TransactionVersion, cairo_version: u64 },
#[error(
"Contract constructor execution has failed:\n{}",
String::from(gen_tx_execution_error_trace(self))
)]
#[error("{}", gen_tx_execution_error_trace(self))]
ContractConstructorExecutionFailed(#[from] ConstructorEntryPointExecutionError),
#[error("Class with hash {:#064x} is already declared.", **class_hash)]
DeclareTransactionError { class_hash: ClassHash },
#[error(
"Transaction execution has failed:\n{}",
String::from(gen_tx_execution_error_trace(self))
)]
#[error("{}", gen_tx_execution_error_trace(self))]
ExecutionError {
error: EntryPointExecutionError,
class_hash: ClassHash,
Expand Down Expand Up @@ -115,10 +109,7 @@ pub enum TransactionExecutionError {
transaction size: {}.", *max_capacity, *tx_size
)]
TransactionTooLarge { max_capacity: Box<BouncerWeights>, tx_size: Box<BouncerWeights> },
#[error(
"Transaction validation has failed:\n{}",
String::from(gen_tx_execution_error_trace(self))
)]
#[error("{}", gen_tx_execution_error_trace(self))]
ValidateTransactionError {
error: EntryPointExecutionError,
class_hash: ClassHash,
Expand Down
22 changes: 15 additions & 7 deletions crates/blockifier/src/transaction/execution_flavors_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,14 @@ fn test_simulate_validate_charge_fee_mid_execution(
.unwrap();
assert_eq!(tx_execution_info.is_reverted(), charge_fee);
if charge_fee {
assert!(tx_execution_info.revert_error.clone().unwrap().contains("no remaining steps"));
assert!(
tx_execution_info
.revert_error
.clone()
.unwrap()
.to_string()
.contains("no remaining steps")
);
}
check_gas_and_fee(
&block_context,
Expand Down Expand Up @@ -676,7 +683,9 @@ fn test_simulate_validate_charge_fee_mid_execution(
})
.execute(&mut state, &low_step_block_context, charge_fee, validate)
.unwrap();
assert!(tx_execution_info.revert_error.clone().unwrap().contains("no remaining steps"));
assert!(
tx_execution_info.revert_error.clone().unwrap().to_string().contains("no remaining steps")
);
// Complete resources used are reported as receipt.resources; but only the charged
// final fee is shown in actual_fee. As a sanity check, verify that the fee derived directly
// from the consumed resources is also equal to the expected fee.
Expand Down Expand Up @@ -764,11 +773,9 @@ fn test_simulate_validate_charge_fee_post_execution(
if charge_fee {
let expected_error_prefix =
&format!("Insufficient max {resource}", resource = Resource::L1Gas);
assert!(tx_execution_info.revert_error.clone().unwrap().starts_with(if is_deprecated {
"Insufficient max fee"
} else {
expected_error_prefix
}));
assert!(tx_execution_info.revert_error.clone().unwrap().to_string().starts_with(
if is_deprecated { "Insufficient max fee" } else { expected_error_prefix }
));
}

check_gas_and_fee(
Expand Down Expand Up @@ -830,6 +837,7 @@ fn test_simulate_validate_charge_fee_post_execution(
.revert_error
.clone()
.unwrap()
.to_string()
.contains("Insufficient fee token balance.")
);
}
Expand Down
Loading
Loading