Skip to content

Commit

Permalink
Merge pull request #8 from Tbelleng/feat/trace_transaction
Browse files Browse the repository at this point in the history
Feat : Trace_transaction RPC call implementation
  • Loading branch information
antiyro committed Mar 6, 2024
2 parents cd75088 + 51257b6 commit 2877687
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 167 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ git # Deoxys Changelog
- feat: add transparent representation to `Felt252Wrapper`
- feat(rpc/trace_api): add `trace_block_transaction`
- chore(db): changed the way hashes are encoded
- feat(rpc/trace_api): add `trace_transaction`

## v0.7.0

Expand Down
4 changes: 4 additions & 0 deletions crates/client/rpc-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,8 @@ pub trait StarknetTraceRpcApi {
#[method(name = "traceBlockTransactions")]
/// Returns the execution traces of all transactions included in the given block
async fn trace_block_transactions(&self, block_id: BlockId) -> RpcResult<Vec<TransactionTraceWithHash>>;

#[method(name = "traceTransaction")]
/// Returns the execution trace of a transaction
async fn trace_transaction(&self, transaction_hash: FieldElement) -> RpcResult<TransactionTraceWithHash>;
}
3 changes: 2 additions & 1 deletion crates/client/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1568,10 +1568,11 @@ where
})
.collect::<Result<Vec<_>, _>>()?;

let empty_transactions = vec![];
let execution_infos = self
.client
.runtime_api()
.re_execute_transactions(previous_substrate_block_hash, transactions.clone())
.re_execute_transactions(previous_substrate_block_hash, empty_transactions, transactions.clone())
.map_err(|e| {
log::error!("Failed to execute runtime API call: {e}");
StarknetRpcApiError::InternalServerError
Expand Down
360 changes: 253 additions & 107 deletions crates/client/rpc/src/trace_api.rs

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions crates/pallets/starknet/runtime_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,18 @@ sp_api::decl_runtime_apis! {
/// the runtime itself, accomplished through the extrinsic_filter method. This enables the
/// client to operate seamlessly while abstracting the extrinsic complexity.
fn extrinsic_filter(xts: Vec<<Block as BlockT>::Extrinsic>) -> Vec<Transaction>;
/// Re-execute a block and return the TransactionExecutionInfos of every transaction in it, in the same order
fn re_execute_transactions(transactions: Vec<UserOrL1HandlerTransaction>) -> Result<Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError>;
/// Used to re-execute transactions from a past block and return their trace
///
/// # Arguments
///
/// * `transactions_before` - The first txs of the block. We don't want to trace those, but we need to execute them to rebuild the exact same state
/// * `transactions_to_trace` - The transactions we want to trace (can be a complete block of transactions or a subset of it)
///
/// # Return
///
/// Idealy, the execution traces of all of `transactions_to_trace`.
/// If any of the transactions (from both arguments) fails, an error is returned.
fn re_execute_transactions(transactions_before: Vec<UserOrL1HandlerTransaction>, transactions_to_trace: Vec<UserOrL1HandlerTransaction>) -> Result<Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError>;

fn get_index_and_tx_for_tx_hash(xts: Vec<<Block as BlockT>::Extrinsic>, chain_id: Felt252Wrapper, tx_hash: Felt252Wrapper) -> Option<(u32, Transaction)>;
/// Returns events, call with index from get_index_and_tx_for_tx_hash method
Expand Down
159 changes: 105 additions & 54 deletions crates/pallets/starknet/src/simulations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,39 @@ impl<T: Config> Pallet<T> {
Ok(tx_execution_results)
}

pub fn simulate_message(
message: HandleL1MessageTransaction,
simulation_flags: &SimulationFlags,
) -> Result<Result<TransactionExecutionInfo, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError> {
storage::transactional::with_transaction(|| {
storage::TransactionOutcome::Rollback(Result::<_, DispatchError>::Ok(Self::simulate_message_inner(
message,
simulation_flags,
)))
})
.map_err(|_| Error::<T>::FailedToCreateATransactionalStorageExecution)?
}

fn simulate_message_inner(
message: HandleL1MessageTransaction,
simulation_flags: &SimulationFlags,
) -> Result<Result<TransactionExecutionInfo, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError> {
let chain_id = Self::chain_id();
let block_context = Self::get_block_context();
let mut execution_config =
RuntimeExecutionConfigBuilder::new::<T>().with_simulation_mode(simulation_flags).build();

// Follow `offset` from Pallet Starknet where it is set to false
execution_config.set_offset_version(false);
let tx_execution_result =
Self::execute_message(message, chain_id, &block_context, &execution_config).map_err(|e| {
log::error!("Transaction execution failed during simulation: {e}");
PlaceHolderErrorTypeForFailedStarknetExecution
});

Ok(tx_execution_result)
}

pub fn estimate_message_fee(message: HandleL1MessageTransaction) -> Result<(u128, u64, u64), DispatchError> {
storage::transactional::with_transaction(|| {
storage::TransactionOutcome::Rollback(Result::<_, DispatchError>::Ok(Self::estimate_message_fee_inner(
Expand All @@ -122,17 +155,30 @@ impl<T: Config> Pallet<T> {
fn estimate_message_fee_inner(message: HandleL1MessageTransaction) -> Result<(u128, u64, u64), DispatchError> {
let chain_id = Self::chain_id();

let tx_execution_infos = message
.into_executable::<T::SystemHash>(chain_id, Fee::default(), true)
.execute(
// Follow `offset` from Pallet Starknet where it is set to false
let tx_execution_infos =
match message.into_executable::<T::SystemHash>(chain_id, Fee(u128::MAX), false).execute(
&mut BlockifierStateAdapter::<T>::default(),
&Self::get_block_context(),
&RuntimeExecutionConfigBuilder::new::<T>().with_query_mode().with_disable_nonce_validation().build(),
)
.map_err(|e| {
log::error!("L1 message execution failed during fee estimation: {}", e);
Error::<T>::TransactionExecutionFailed
})?;
) {
Ok(execution_info) if !execution_info.is_reverted() => Ok(execution_info),
Err(e) => {
log::error!(
"Transaction execution failed during fee estimation: {e} {:?}",
std::error::Error::source(&e)
);
Err(Error::<T>::TransactionExecutionFailed)
}
Ok(execution_info) => {
log::error!(
"Transaction execution reverted during fee estimation: {}",
// Safe due to the `match` branch order
execution_info.revert_error.unwrap()
);
Err(Error::<T>::TransactionExecutionFailed)
}
}?;

if let Some(l1_gas_usage) = tx_execution_infos.actual_resources.0.get("l1_gas_usage") {
Ok((T::L1GasPrice::get().price_in_wei, tx_execution_infos.actual_fee.0 as u64, *l1_gas_usage))
Expand All @@ -142,69 +188,42 @@ impl<T: Config> Pallet<T> {
}

pub fn re_execute_transactions(
transactions: Vec<UserOrL1HandlerTransaction>,
transactions_before: Vec<UserOrL1HandlerTransaction>,
transactions_to_trace: Vec<UserOrL1HandlerTransaction>,
) -> Result<Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError>
{
storage::transactional::with_transaction(|| {
storage::TransactionOutcome::Rollback(Result::<_, DispatchError>::Ok(Self::re_execute_transactions_inner(
transactions,
transactions_before,
transactions_to_trace,
)))
})
.map_err(|_| Error::<T>::FailedToCreateATransactionalStorageExecution)?
}

fn re_execute_transactions_inner(
transactions: Vec<UserOrL1HandlerTransaction>,
transactions_before: Vec<UserOrL1HandlerTransaction>,
transactions_to_trace: Vec<UserOrL1HandlerTransaction>,
) -> Result<Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError>
{
let chain_id = Self::chain_id();
let block_context = Self::get_block_context();
let execution_config = RuntimeExecutionConfigBuilder::new::<T>().build();

let execution_infos = transactions
.iter()
.map(|user_or_l1_tx| match user_or_l1_tx {
UserOrL1HandlerTransaction::User(tx) => match tx {
UserTransaction::Declare(tx, contract_class) => tx
.try_into_executable::<T::SystemHash>(chain_id, contract_class.clone(), false)
.map_err(|e| {
log::error!("Failed to reexecute a tx: {}", e);
PlaceHolderErrorTypeForFailedStarknetExecution
})
.and_then(|executable| {
executable
.execute(&mut BlockifierStateAdapter::<T>::default(), &block_context, &execution_config)
.map_err(|e| {
log::error!("Failed to reexecute a tx: {}", e);
PlaceHolderErrorTypeForFailedStarknetExecution
})
}),
UserTransaction::DeployAccount(tx) => tx
.into_executable::<T::SystemHash>(chain_id, false)
.execute(&mut BlockifierStateAdapter::<T>::default(), &block_context, &execution_config)
.map_err(|e| {
log::error!("Failed to reexecute a tx: {}", e);
PlaceHolderErrorTypeForFailedStarknetExecution
}),
UserTransaction::Invoke(tx) => tx
.into_executable::<T::SystemHash>(chain_id, false)
.execute(&mut BlockifierStateAdapter::<T>::default(), &block_context, &execution_config)
.map_err(|e| {
log::error!("Failed to reexecute a tx: {}", e);
PlaceHolderErrorTypeForFailedStarknetExecution
}),
},
UserOrL1HandlerTransaction::L1Handler(tx, fee) => tx
.into_executable::<T::SystemHash>(chain_id, *fee, false)
.execute(&mut BlockifierStateAdapter::<T>::default(), &block_context, &execution_config)
.map_err(|e| {
log::error!("Failed to reexecute a tx: {}", e);
PlaceHolderErrorTypeForFailedStarknetExecution
}),
})
.collect::<Result<Vec<_>, _>>();
let _transactions_before_exec_infos = Self::execute_user_or_l1_handler_transactions(
chain_id,
&block_context,
&execution_config,
transactions_before,
);
let transactions_exec_infos = Self::execute_user_or_l1_handler_transactions(
chain_id,
&block_context,
&execution_config,
transactions_to_trace,
);

Ok(execution_infos)
Ok(transactions_exec_infos)
}

fn execute_user_transaction(
Expand All @@ -229,4 +248,36 @@ impl<T: Config> Pallet<T> {
}
}
}

fn execute_message(
message: HandleL1MessageTransaction,
chain_id: Felt252Wrapper,
block_context: &BlockContext,
execution_config: &ExecutionConfig,
) -> Result<TransactionExecutionInfo, TransactionExecutionError> {
// Follow `offset` from Pallet Starknet where it is set to false
let fee = Fee(u128::MAX);
let executable = message.into_executable::<T::SystemHash>(chain_id, fee, false);
executable.execute(&mut BlockifierStateAdapter::<T>::default(), block_context, execution_config)
}

fn execute_user_or_l1_handler_transactions(
chain_id: Felt252Wrapper,
block_context: &BlockContext,
execution_config: &ExecutionConfig,
transactions: Vec<UserOrL1HandlerTransaction>,
) -> Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution> {
transactions
.iter()
.map(|user_or_l1_tx| match user_or_l1_tx {
UserOrL1HandlerTransaction::User(tx) => {
Self::execute_user_transaction(tx.clone(), chain_id, block_context, execution_config)
}
UserOrL1HandlerTransaction::L1Handler(tx, _fee) => {
Self::execute_message(tx.clone(), chain_id, block_context, execution_config)
}
})
.collect::<Result<Vec<_>, _>>()
.map_err(|_| PlaceHolderErrorTypeForFailedStarknetExecution)
}
}
113 changes: 112 additions & 1 deletion crates/pallets/starknet/src/tests/re_execute_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ fn re_execute_tx_ok() {
UserOrL1HandlerTransaction::L1Handler(handle_l1_tx, Fee(10)),
];

let txs_to_ignore: Vec<UserOrL1HandlerTransaction> = vec![];

// Call the function we want to test
let res = Starknet::re_execute_transactions(txs.clone()).unwrap().unwrap();
let res = Starknet::re_execute_transactions(txs_to_ignore, txs.clone()).unwrap().unwrap();

// Storage changes have been reverted
assert_eq!(Starknet::nonce(invoke_sender_address), Nonce(Felt252Wrapper::ZERO.into()));
Expand Down Expand Up @@ -158,3 +160,112 @@ fn re_execute_tx_ok() {
assert_eq!(res[4], handle_l1_message_tx_info);
});
}

// Desactivate until i found i make this test work
// #[test]
// fn re_execute_tx_with_a_transfer_ok() {
// new_test_ext::<MockRuntime>().execute_with(|| {
// basic_test_setup(2);
// let invoke_sender_address: ContractAddress =
// Felt252Wrapper::from_hex_be(constants::BLOCKIFIER_ACCOUNT_ADDRESS).unwrap().into();
// let chain_id = Starknet::chain_id();

// // Deploy

// // TEST ACCOUNT CONTRACT
// // - ref testnet tx(0x0751b4b5b95652ad71b1721845882c3852af17e2ed0c8d93554b5b292abb9810)
// let salt =
//
// Felt252Wrapper::from_hex_be("0x03b37cbe4e9eac89d54c5f7cc6329a63a63e8c8db2bf936f981041e086752463"
// ).unwrap(); let (account_class_hash, calldata) =
// account_helper(AccountType::V0(AccountTypeV0Inner::NoValidate));

// let deploy_tx = DeployAccountTransaction {
// nonce: Felt252Wrapper::ZERO,
// max_fee: u128::MAX,
// signature: vec![],
// contract_address_salt: salt,
// constructor_calldata: calldata.0.iter().map(|e| Felt252Wrapper::from(*e)).collect(),
// class_hash: account_class_hash.into(),
// offset_version: false,
// };

// let address = deploy_tx.account_address().into();
// set_infinite_tokens::<MockRuntime>(&address);

// // Declare

// let declare_tx =
// get_declare_dummy(chain_id, Felt252Wrapper::ZERO,
// AccountType::V0(AccountTypeV0Inner::Openzeppelin)); let erc20_class_hash: CasmClassHash =
//
// Felt252Wrapper::from_hex_be("0x372ee6669dc86563007245ed7343d5180b96221ce28f44408cff2898038dbd4")
// .unwrap()
// .into();
// let erc20_class = get_contract_class("ERC20.json", 0);

// let contract_address =
//
// Felt252Wrapper::from_hex_be("0x024d1e355f6b9d27a5a420c8f4b50cea9154a8e34ad30fc39d7c98d3c177d0d7"
// ).unwrap(); let from_address =
// Felt252Wrapper::from_hex_be("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").unwrap();

// // Handle l1 message

// let handle_l1_tx = HandleL1MessageTransaction {
// nonce: 1,
// contract_address,
// entry_point_selector: Felt252Wrapper::from_hex_be(
// "0x014093c40d95d0a3641c087f7d48d55160e1a58bc7c07b0d2323efeeb3087269", //
// test_l1_handler_store_under_caller_address )
// .unwrap(),
// calldata: vec![
// from_address,
// Felt252Wrapper::from_hex_be("0x1").unwrap(), // value
// ],
// };

// let txs = vec![
// UserOrL1HandlerTransaction::User(mp_transactions::UserTransaction::Invoke(
// get_invoke_dummy(Felt252Wrapper::ZERO).into(),
// )),
// UserOrL1HandlerTransaction::User(mp_transactions::UserTransaction::Invoke(
// get_invoke_dummy(Felt252Wrapper::ONE).into(),
// )),
//
// UserOrL1HandlerTransaction::User(mp_transactions::UserTransaction::Declare(declare_tx,
// erc20_class)),
// UserOrL1HandlerTransaction::User(mp_transactions::UserTransaction::DeployAccount(deploy_tx)),
// UserOrL1HandlerTransaction::L1Handler(handle_l1_tx, Fee(10)),
// ];

// let transfer_tx: Vec<UserOrL1HandlerTransaction> = vec![UserOrL1HandlerTransaction::User(
//
// mp_transactions::UserTransaction::Invoke(get_invoke_dummy(Felt252Wrapper::TWO).into()),
// )];

// // Call the function we want to test
// let res = Starknet::re_execute_transactions(txs.clone(),
// transfer_tx.clone()).unwrap().unwrap();

// // Storage changes have been reverted
// assert_eq!(Starknet::nonce(invoke_sender_address), Nonce(Felt252Wrapper::ZERO.into()));
// assert_eq!(Starknet::contract_class_by_class_hash(erc20_class_hash), None);
// // Here we only got the transfer tx
// assert_eq!(res.len(), 1);

// // Now let's check the TransactionInfos returned
// let transfer_invoke_tx_info = match transfer_tx.get(0).unwrap() {
// UserOrL1HandlerTransaction::User(mp_transactions::UserTransaction::Invoke(invoke_tx))
// => invoke_tx .into_executable::<<MockRuntime as Config>::SystemHash>(chain_id,
// false) .execute(
// &mut BlockifierStateAdapter::<MockRuntime>::default(),
// &Starknet::get_block_context(),
// &RuntimeExecutionConfigBuilder::new::<MockRuntime>().build(),
// )
// .unwrap(),
// _ => unreachable!(),
// };
// assert_eq!(res[0], transfer_invoke_tx_info);
// });
// }
4 changes: 2 additions & 2 deletions crates/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ impl_runtime_apis! {
Starknet::estimate_fee(transactions)
}

fn re_execute_transactions(transactions: Vec<UserOrL1HandlerTransaction>) -> Result<Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError> {
Starknet::re_execute_transactions(transactions)
fn re_execute_transactions(transactions_before: Vec<UserOrL1HandlerTransaction>, transactions_to_trace: Vec<UserOrL1HandlerTransaction>) -> Result<Result<Vec<TransactionExecutionInfo>, PlaceHolderErrorTypeForFailedStarknetExecution>, DispatchError> {
Starknet::re_execute_transactions(transactions_before, transactions_to_trace)
}

fn estimate_message_fee(message: HandleL1MessageTransaction) -> Result<(u128, u64, u64), DispatchError> {
Expand Down

0 comments on commit 2877687

Please sign in to comment.