Skip to content
This repository has been archived by the owner on Aug 2, 2024. It is now read-only.

Commit

Permalink
feat: add rpc traceBlock
Browse files Browse the repository at this point in the history
  • Loading branch information
tdelabro committed Jan 29, 2024
1 parent 24f4cec commit 46465ef
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 112 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Next release

- feat(rpc/trace_api): add `trace_block_transaction`
- chore(runtime_api): remove unused `filter_extrinsic`
- fix: don't ignore Sierra to CASM mapping in genesis config
- refacto: early exit txs fee estimation when one fails
- dev: fix linter warning in README.md
Expand Down Expand Up @@ -61,7 +63,7 @@
- feat: fixing getNonce Rpc Call and adding a new test
- refactor: use Zaun crate for Starknet core contract bindings
- refactor: use Anvil sandbox from Zaun crate
- feat(rpc) : estimateMessageFee RPC call implementation
- feat(rpc): estimateMessageFee RPC call implementation

## v0.6.0

Expand Down
5 changes: 5 additions & 0 deletions crates/client/rpc-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use starknet_core::types::{
DeployAccountTransactionResult, EventFilterWithPage, EventsPage, FeeEstimate, FieldElement, FunctionCall,
InvokeTransactionResult, MaybePendingBlockWithTxHashes, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt,
MsgFromL1, SimulatedTransaction, SimulationFlag, StateUpdate, SyncStatusType, Transaction,
TransactionTraceWithHash,
};

#[serde_as]
Expand Down Expand Up @@ -178,4 +179,8 @@ pub trait StarknetTraceRpcApi {
transactions: Vec<BroadcastedTransaction>,
simulation_flags: Vec<SimulationFlag>,
) -> RpcResult<Vec<SimulatedTransaction>>;

#[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>>;
}
222 changes: 153 additions & 69 deletions crates/client/rpc/src/trace_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use blockifier::transaction::objects::TransactionExecutionInfo;
use jsonrpsee::core::{async_trait, RpcResult};
use log::error;
use mc_genesis_data_provider::GenesisProvider;
use mc_rpc_core::utils::get_block_by_block_hash;
use mc_rpc_core::{StarknetReadRpcApiServer, StarknetTraceRpcApiServer};
use mc_storage::StorageOverride;
use mp_felt::Felt252Wrapper;
Expand All @@ -19,7 +20,8 @@ use sp_blockchain::HeaderBackend;
use sp_runtime::traits::Block as BlockT;
use starknet_core::types::{
BlockId, BroadcastedTransaction, DeclareTransactionTrace, DeployAccountTransactionTrace, ExecuteInvocation,
FeeEstimate, InvokeTransactionTrace, RevertedInvocation, SimulatedTransaction, SimulationFlag, TransactionTrace,
FeeEstimate, InvokeTransactionTrace, L1HandlerTransactionTrace, RevertedInvocation, SimulatedTransaction,
SimulationFlag, TransactionTrace, TransactionTraceWithHash,
};
use starknet_ff::FieldElement;
use thiserror::Error;
Expand Down Expand Up @@ -85,17 +87,73 @@ where
let storage_override = self.overrides.for_block_hash(self.client.as_ref(), substrate_block_hash);
let simulated_transactions =
tx_execution_infos_to_simulated_transactions(&**storage_override, substrate_block_hash, tx_types, res)
.map_err(|e| match e {
ConvertCallInfoToExecuteInvocationError::TransactionExecutionFailed => {
StarknetRpcApiError::ContractError
}
ConvertCallInfoToExecuteInvocationError::GetFunctionInvocation(_) => {
StarknetRpcApiError::InternalServerError
}
})?;
.map_err(|e| StarknetRpcApiError::from(e))?;

Ok(simulated_transactions)
}

async fn trace_block_transactions(&self, block_id: BlockId) -> RpcResult<Vec<TransactionTraceWithHash>> {
let substrate_block_hash = self.substrate_block_hash_from_starknet_block(block_id).map_err(|e| {
error!("Block not found: '{e}'");
StarknetRpcApiError::BlockNotFound
})?;
let block_extrinsics = self
.client
.block_body(substrate_block_hash)
.map_err(|e| {
error!("Failed to get block body. Substrate block hash: {substrate_block_hash}, error: {e}");
StarknetRpcApiError::InternalServerError
})?
.ok_or(StarknetRpcApiError::BlockNotFound)?;

let previous_block_substrate_hash = {
let starknet_block =
get_block_by_block_hash(self.client.as_ref(), substrate_block_hash).ok_or_else(|| {
error!("Failed to starknet block for substate block with hash {substrate_block_hash}");
StarknetRpcApiError::InternalServerError
})?;
let block_number = starknet_block.header().block_number;
let previous_block_number = block_number - 1;
self.substrate_block_hash_from_starknet_block(BlockId::Number(previous_block_number)).map_err(|e| {
error!("Failed to retrieve previous block substrate hash: {e}");
StarknetRpcApiError::InternalServerError
})
}?;

let execution_infos = self
.client
.runtime_api()
.re_execute_transactions(previous_block_substrate_hash, block_extrinsics)
.map_err(|e| {
error!("Failed to execute runtime API call: {e}");
StarknetRpcApiError::InternalServerError
})?
.map_err(|e| {
error!("Failed to reexecute the block transactions: {e:?}");
StarknetRpcApiError::InternalServerError
})?
.map_err(|_| {
error!(
"One of the transaction failed during it's reexecution. This should not happen, as the block has \
already been executed successfully in the past. There is a bug somewhere."
);
StarknetRpcApiError::InternalServerError
})?;

let storage_override = self.overrides.for_block_hash(self.client.as_ref(), substrate_block_hash);
let traces = execution_infos
.into_iter()
.map(|(tx_hash, tx_type, tx_exec_info)| {
tx_execution_infos_to_tx_trace(&**storage_override, substrate_block_hash, tx_type, &tx_exec_info)
.and_then(|trace_root| {
Ok(TransactionTraceWithHash { transaction_hash: tx_hash.into(), trace_root })
})
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| StarknetRpcApiError::from(e))?;

Ok(traces)
}
}

#[derive(Error, Debug)]
Expand All @@ -106,6 +164,17 @@ pub enum ConvertCallInfoToExecuteInvocationError {
GetFunctionInvocation(#[from] TryFuntionInvocationFromCallInfoError),
}

impl From<ConvertCallInfoToExecuteInvocationError> for StarknetRpcApiError {
fn from(err: ConvertCallInfoToExecuteInvocationError) -> Self {
match err {
ConvertCallInfoToExecuteInvocationError::TransactionExecutionFailed => StarknetRpcApiError::ContractError,
ConvertCallInfoToExecuteInvocationError::GetFunctionInvocation(_) => {
StarknetRpcApiError::InternalServerError
}
}
}
}

fn collect_call_info_ordered_messages(call_info: &CallInfo) -> Vec<starknet_core::types::OrderedMessage> {
call_info
.execution
Expand Down Expand Up @@ -209,6 +278,78 @@ fn try_get_funtion_invocation_from_call_info<B: BlockT>(
})
}

fn tx_execution_infos_to_tx_trace<B: BlockT>(
storage_override: &dyn StorageOverride<B>,
substrate_block_hash: B::Hash,
tx_type: TxType,
tx_exec_info: &TransactionExecutionInfo,
) -> Result<TransactionTrace, ConvertCallInfoToExecuteInvocationError> {
// If simulated with `SimulationFlag::SkipValidate` this will be `None`
// therefore we cannot unwrap it
let validate_invocation = tx_exec_info
.validate_call_info
.as_ref()
.map(|call_info| try_get_funtion_invocation_from_call_info(storage_override, substrate_block_hash, call_info))
.transpose()?;
// If simulated with `SimulationFlag::SkipFeeCharge` this will be `None`
// therefore we cannot unwrap it
let fee_transfer_invocation = tx_exec_info
.fee_transfer_call_info
.as_ref()
.map(|call_info| try_get_funtion_invocation_from_call_info(storage_override, substrate_block_hash, call_info))
.transpose()?;

let tx_trace = match tx_type {
TxType::Invoke => TransactionTrace::Invoke(InvokeTransactionTrace {
validate_invocation,
execute_invocation: if let Some(e) = &tx_exec_info.revert_error {
ExecuteInvocation::Reverted(RevertedInvocation { revert_reason: e.clone() })
} else {
ExecuteInvocation::Success(try_get_funtion_invocation_from_call_info(
storage_override,
substrate_block_hash,
// Safe to unwrap because is only `None` for `Declare` txs
tx_exec_info.execute_call_info.as_ref().unwrap(),
)?)
},
fee_transfer_invocation,
// TODO(#1291): Compute state diff correctly
state_diff: None,
}),
TxType::Declare => TransactionTrace::Declare(DeclareTransactionTrace {
validate_invocation,
fee_transfer_invocation,
// TODO(#1291): Compute state diff correctly
state_diff: None,
}),
TxType::DeployAccount => {
TransactionTrace::DeployAccount(DeployAccountTransactionTrace {
validate_invocation,
constructor_invocation: try_get_funtion_invocation_from_call_info(
storage_override,
substrate_block_hash,
// Safe to unwrap because is only `None` for `Declare` txs
tx_exec_info.execute_call_info.as_ref().unwrap(),
)?,
fee_transfer_invocation,
// TODO(#1291): Compute state diff correctly
state_diff: None,
})
}
TxType::L1Handler => TransactionTrace::L1Handler(L1HandlerTransactionTrace {
function_invocation: try_get_funtion_invocation_from_call_info(
storage_override,
substrate_block_hash,
// Safe to unwrap because is only `None` for `Declare` txs
tx_exec_info.execute_call_info.as_ref().unwrap(),
)?,
state_diff: None,
}),
};

Ok(tx_trace)
}

fn tx_execution_infos_to_simulated_transactions<B: BlockT>(
storage_override: &dyn StorageOverride<B>,
substrate_block_hash: B::Hash,
Expand All @@ -218,68 +359,11 @@ fn tx_execution_infos_to_simulated_transactions<B: BlockT>(
>,
) -> Result<Vec<SimulatedTransaction>, ConvertCallInfoToExecuteInvocationError> {
let mut results = vec![];
for (tx_type, res) in tx_types.iter().zip(transaction_execution_results.iter()) {
for (tx_type, res) in tx_types.into_iter().zip(transaction_execution_results.into_iter()) {
match res {
Ok(tx_exec_info) => {
// If simulated with `SimulationFlag::SkipValidate` this will be `None`
// therefore we cannot unwrap it
let validate_invocation = tx_exec_info
.validate_call_info
.as_ref()
.map(|call_info| {
try_get_funtion_invocation_from_call_info(storage_override, substrate_block_hash, call_info)
})
.transpose()?;
// If simulated with `SimulationFlag::SkipFeeCharge` this will be `None`
// therefore we cannot unwrap it
let fee_transfer_invocation = tx_exec_info
.fee_transfer_call_info
.as_ref()
.map(|call_info| {
try_get_funtion_invocation_from_call_info(storage_override, substrate_block_hash, call_info)
})
.transpose()?;

let transaction_trace = match tx_type {
TxType::Invoke => TransactionTrace::Invoke(InvokeTransactionTrace {
validate_invocation,
execute_invocation: if let Some(e) = &tx_exec_info.revert_error {
ExecuteInvocation::Reverted(RevertedInvocation { revert_reason: e.clone() })
} else {
ExecuteInvocation::Success(try_get_funtion_invocation_from_call_info(
storage_override,
substrate_block_hash,
// Safe to unwrap because is only `None` for `Declare` txs
tx_exec_info.execute_call_info.as_ref().unwrap(),
)?)
},
fee_transfer_invocation,
// TODO(#1291): Compute state diff correctly
state_diff: None,
}),
TxType::Declare => TransactionTrace::Declare(DeclareTransactionTrace {
validate_invocation,
fee_transfer_invocation,
// TODO(#1291): Compute state diff correctly
state_diff: None,
}),
TxType::DeployAccount => {
TransactionTrace::DeployAccount(DeployAccountTransactionTrace {
validate_invocation,
constructor_invocation: try_get_funtion_invocation_from_call_info(
storage_override,
substrate_block_hash,
// Safe to unwrap because is only `None` for `Declare` txs
tx_exec_info.execute_call_info.as_ref().unwrap(),
)?,
fee_transfer_invocation,
// TODO(#1291): Compute state diff correctly
state_diff: None,
})
}
TxType::L1Handler => unreachable!("L1Handler transactions cannot be simulated"),
};

let transaction_trace =
tx_execution_infos_to_tx_trace(storage_override, substrate_block_hash, tx_type, &tx_exec_info)?;
let gas_consumed =
tx_exec_info.execute_call_info.as_ref().map(|x| x.execution.gas_consumed).unwrap_or_default();
let overall_fee = tx_exec_info.actual_fee.0 as u64;
Expand Down
12 changes: 3 additions & 9 deletions crates/pallets/starknet/runtime_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use alloc::string::String;
use alloc::vec::Vec;

use mp_simulations::{PlaceHolderErrorTypeForFailedStarknetExecution, SimulationFlags};
use mp_transactions::TxType;
use sp_runtime::DispatchError;
use starknet_api::api_core::{ChainId, ClassHash, ContractAddress, EntryPointSelector, Nonce};
use starknet_api::block::{BlockNumber, BlockTimestamp};
Expand Down Expand Up @@ -60,15 +61,8 @@ sp_api::decl_runtime_apis! {
fn estimate_message_fee(message: HandleL1MessageTransaction) -> Result<(u128, u64, u64), DispatchError>;
/// Simulates transactions and returns their trace
fn simulate_transactions(transactions: Vec<UserTransaction>, simulation_flags: SimulationFlags) -> Result<Vec<Result<TransactionExecutionInfo, PlaceHolderErrorTypeForFailedStarknetExecution>>, DispatchError>;
/// Filters extrinsic transactions to return only Starknet transactions
///
/// To support runtime upgrades, the client must be unaware of the specific extrinsic
/// details. To achieve this, the client uses an OpaqueExtrinsic type to represent and
/// manipulate extrinsics. However, the client cannot decode and filter extrinsics due to
/// this limitation. The solution is to offload decoding and filtering to the RuntimeApi in
/// 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
fn re_execute_transactions(xts: Vec<<Block as BlockT>::Extrinsic>) -> Result<Result<Vec<(Felt252Wrapper, TxType, 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
fn get_events_for_tx_by_index(tx_index: u32) -> Option<Vec<StarknetEvent>>;
Expand Down
6 changes: 3 additions & 3 deletions crates/pallets/starknet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ use mp_storage::{StarknetStorageSchemaVersion, PALLET_STARKNET_SCHEMA};
use mp_transactions::execution::Execute;
use mp_transactions::{
DeclareTransaction, DeployAccountTransaction, HandleL1MessageTransaction, InvokeTransaction, Transaction,
UserAndL1HandlerTransaction, UserTransaction,
UserOrL1HandlerTransaction, UserTransaction,
};
use sp_runtime::traits::UniqueSaturatedInto;
use sp_runtime::DigestItem;
Expand Down Expand Up @@ -787,15 +787,15 @@ impl<T: Config> Pallet<T> {
/// # Returns
///
/// The transaction
fn get_call_transaction(call: Call<T>) -> Result<UserAndL1HandlerTransaction, ()> {
fn get_call_transaction(call: Call<T>) -> Result<UserOrL1HandlerTransaction, ()> {
let tx = match call {
Call::<T>::invoke { transaction } => UserTransaction::Invoke(transaction).into(),
Call::<T>::declare { transaction, contract_class } => {
UserTransaction::Declare(transaction, contract_class).into()
}
Call::<T>::deploy_account { transaction } => UserTransaction::DeployAccount(transaction).into(),
Call::<T>::consume_l1_message { transaction, paid_fee_on_l1 } => {
UserAndL1HandlerTransaction::L1Handler(transaction, paid_fee_on_l1)
UserOrL1HandlerTransaction::L1Handler(transaction, paid_fee_on_l1)
}
_ => return Err(()),
};
Expand Down
Loading

0 comments on commit 46465ef

Please sign in to comment.