Skip to content

Commit

Permalink
refacto: early exit estimate fee in runtime (keep-starknet-strange#1382)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdelabro committed Jan 25, 2024
1 parent 50d5120 commit 523ceed
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 139 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Next release

- refacto: early exit txs fee estimation when one fails
- dev: fix linter warning in README.md
- fix: remove waiting loop from `getTxReceipt`
- feat: types in `mp-transactions` impl a method to get their version
Expand Down
7 changes: 5 additions & 2 deletions crates/client/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,8 +1029,11 @@ where
StarknetRpcApiError::ContractError
})?;

let estimate =
FeeEstimate { gas_price: fee_estimate.0, gas_consumed: fee_estimate.2, overall_fee: fee_estimate.1 };
let estimate = FeeEstimate {
gas_price: fee_estimate.0.try_into().map_err(|_| StarknetRpcApiError::InternalServerError)?,
gas_consumed: fee_estimate.2,
overall_fee: fee_estimate.1,
};

Ok(estimate)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/pallets/starknet/runtime_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ sp_api::decl_runtime_apis! {
/// Returns fee estimate
fn estimate_fee(transactions: Vec<UserTransaction>) -> Result<Vec<(u64, u64)>, DispatchError>;
/// Returns message fee estimate
fn estimate_message_fee(message: HandleL1MessageTransaction) -> Result<(u64, u64, u64), DispatchError>;
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
Expand Down
80 changes: 3 additions & 77 deletions crates/pallets/starknet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ pub mod blockifier_state_adapter;
pub mod execution_config;
#[cfg(feature = "std")]
pub mod genesis_loader;
/// Simulation, estimations and execution trace logic.
pub mod simulations;
/// Transaction validation logic.
pub mod transaction_validation;
/// The Starknet pallet's runtime custom types.
pub mod types;

#[cfg(test)]
mod tests;
mod utils;

#[macro_use]
pub extern crate alloc;
Expand All @@ -70,7 +71,6 @@ use blockifier::execution::entry_point::{
};
use blockifier::execution::errors::{EntryPointExecutionError, PreExecutionError};
use blockifier::state::cached_state::ContractStorageKey;
use blockifier::transaction::objects::TransactionExecutionInfo;
use blockifier_state_adapter::BlockifierStateAdapter;
use frame_support::pallet_prelude::*;
use frame_support::traits::Time;
Expand All @@ -81,7 +81,6 @@ use mp_fee::{ResourcePrice, INITIAL_GAS};
use mp_felt::Felt252Wrapper;
use mp_hashers::HasherT;
use mp_sequencer_address::{InherentError, InherentType, DEFAULT_SEQUENCER_ADDRESS, INHERENT_IDENTIFIER};
use mp_simulations::{PlaceHolderErrorTypeForFailedStarknetExecution, SimulationFlags};
use mp_storage::{StarknetStorageSchemaVersion, PALLET_STARKNET_SCHEMA};
use mp_transactions::execution::Execute;
use mp_transactions::{
Expand All @@ -102,7 +101,6 @@ use transaction_validation::TxPriorityInfo;
use crate::alloc::string::ToString;
use crate::execution_config::RuntimeExecutionConfigBuilder;
use crate::types::{CasmClassHash, SierraClassHash, StorageSlot};
use crate::utils::execute_txs_and_rollback;

pub(crate) const LOG_TARGET: &str = "runtime::starknet";

Expand Down Expand Up @@ -440,6 +438,7 @@ pub mod pallet {
MissingCallInfo,
FailedToCreateATransactionalStorageExecution,
L1MessageAlreadyExecuted,
MissingL1GasUsage,
}

/// The Starknet pallet external functions.
Expand Down Expand Up @@ -1075,79 +1074,6 @@ impl<T: Config> Pallet<T> {
next_order
}

/// Estimate the fee associated with transaction
pub fn estimate_fee(transactions: Vec<UserTransaction>) -> Result<Vec<(u64, u64)>, DispatchError> {
let chain_id = Self::chain_id();

let execution_results = execute_txs_and_rollback::<T>(
&transactions,
&Self::get_block_context(),
chain_id,
&mut RuntimeExecutionConfigBuilder::new::<T>().with_query_mode().build(),
)?;

let mut results = vec![];
for res in execution_results {
match res {
Ok(tx_exec_info) => {
log!(info, "Successfully estimated fee: {:?}", tx_exec_info);
if let Some(l1_gas_usage) = tx_exec_info.actual_resources.0.get("l1_gas_usage") {
results.push((tx_exec_info.actual_fee.0 as u64, *l1_gas_usage));
} else {
return Err(Error::<T>::TransactionExecutionFailed.into());
}
}
Err(e) => {
log!(info, "Failed to estimate fee: {:?}", e);
return Err(Error::<T>::TransactionExecutionFailed.into());
}
}
}
Ok(results)
}

/// Warning : this should only be called from the runtimeAPI, never from inside an extrinsic
/// execution flow
pub fn estimate_message_fee(message: HandleL1MessageTransaction) -> Result<(u64, u64, u64), DispatchError> {
let chain_id = Self::chain_id();
let transaction = message.into_executable::<T::SystemHash>(chain_id, Fee::default(), true);
let gas_price: u64 = T::L1GasPrice::get().price_in_wei.try_into().unwrap();

let tx_execution_infos = transaction
.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!("failed to execute invoke tx: {:?}", e);
Error::<T>::TransactionExecutionFailed
})?;

if let Some(l1_gas_usage) = tx_execution_infos.actual_resources.0.get("l1_gas_usage") {
Ok((gas_price, tx_execution_infos.actual_fee.0 as u64, *l1_gas_usage))
} else {
Err(Error::<T>::TransactionExecutionFailed.into())
}
}

pub fn simulate_transactions(
transactions: Vec<UserTransaction>,
simulation_flags: SimulationFlags,
) -> Result<Vec<Result<TransactionExecutionInfo, PlaceHolderErrorTypeForFailedStarknetExecution>>, DispatchError>
{
let chain_id = Self::chain_id();

let tx_execution_results = execute_txs_and_rollback::<T>(
&transactions,
&Self::get_block_context(),
chain_id,
&mut RuntimeExecutionConfigBuilder::new::<T>().with_simulation_mode(&simulation_flags).build(),
)?;

Ok(tx_execution_results)
}

pub fn emit_and_store_tx_and_fees_events(
tx_hash: TransactionHash,
execute_call_info: &Option<CallInfo>,
Expand Down
166 changes: 166 additions & 0 deletions crates/pallets/starknet/src/simulations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use alloc::vec::Vec;

use blockifier::block_context::BlockContext;
use blockifier::transaction::errors::TransactionExecutionError;
use blockifier::transaction::objects::TransactionExecutionInfo;
use frame_support::storage;
use mp_felt::Felt252Wrapper;
use mp_simulations::{PlaceHolderErrorTypeForFailedStarknetExecution, SimulationFlags};
use mp_transactions::execution::{Execute, ExecutionConfig};
use mp_transactions::{HandleL1MessageTransaction, UserTransaction};
use sp_core::Get;
use sp_runtime::DispatchError;
use starknet_api::transaction::Fee;

use crate::blockifier_state_adapter::BlockifierStateAdapter;
use crate::execution_config::RuntimeExecutionConfigBuilder;
use crate::{Config, Error, Pallet};

impl<T: Config> Pallet<T> {
pub fn estimate_fee(transactions: Vec<UserTransaction>) -> Result<Vec<(u64, u64)>, DispatchError> {
storage::transactional::with_transaction(|| {
storage::TransactionOutcome::Rollback(Result::<_, DispatchError>::Ok(Self::estimate_fee_inner(
transactions,
)))
})
.map_err(|_| Error::<T>::FailedToCreateATransactionalStorageExecution)?
}

fn estimate_fee_inner(transactions: Vec<UserTransaction>) -> Result<Vec<(u64, u64)>, DispatchError> {
let transactions_len = transactions.len();
let chain_id = Self::chain_id();
let block_context = Self::get_block_context();
let mut execution_config = RuntimeExecutionConfigBuilder::new::<T>().with_query_mode().build();

let fee_res_iterator = transactions
.into_iter()
.map(|tx| {
execution_config.set_offset_version(tx.offset_version());

match Self::execute_transaction(tx, chain_id, &block_context, &execution_config) {
Ok(execution_info) if !execution_info.is_reverted() => Ok(execution_info),
Err(e) => {
log::error!("Transaction execution failed during fee estimation: {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)
}
}
})
.map(|exec_info_res| {
exec_info_res.map(|exec_info| {
exec_info
.actual_resources
.0
.get("l1_gas_usage")
.ok_or_else(|| DispatchError::from(Error::<T>::MissingL1GasUsage))
.map(|l1_gas_usage| (exec_info.actual_fee.0 as u64, *l1_gas_usage))
})
});

let mut fees = Vec::with_capacity(transactions_len);
for fee_res in fee_res_iterator {
fees.push(fee_res??);
}

Ok(fees)
}
pub fn simulate_transactions(
transactions: Vec<UserTransaction>,
simulation_flags: &SimulationFlags,
) -> Result<Vec<Result<TransactionExecutionInfo, PlaceHolderErrorTypeForFailedStarknetExecution>>, DispatchError>
{
storage::transactional::with_transaction(|| {
storage::TransactionOutcome::Rollback(Result::<_, DispatchError>::Ok(Self::simulate_transactions_inner(
transactions,
simulation_flags,
)))
})
.map_err(|_| Error::<T>::FailedToCreateATransactionalStorageExecution)?
}

fn simulate_transactions_inner(
transactions: Vec<UserTransaction>,
simulation_flags: &SimulationFlags,
) -> Result<Vec<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();

let tx_execution_results = transactions
.into_iter()
.map(|tx| {
execution_config.set_offset_version(tx.offset_version());

Self::execute_transaction(tx, chain_id, &block_context, &execution_config).map_err(|e| {
log::error!("Transaction execution failed during simulation: {e}");
PlaceHolderErrorTypeForFailedStarknetExecution
})
})
.collect();

Ok(tx_execution_results)
}

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(
message,
)))
})
.map_err(|_| Error::<T>::FailedToCreateATransactionalStorageExecution)?
}

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(
&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
})?;

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))
} else {
Err(Error::<T>::MissingL1GasUsage.into())
}
}

fn execute_transaction(
transaction: UserTransaction,
chain_id: Felt252Wrapper,
block_context: &BlockContext,
execution_config: &ExecutionConfig,
) -> Result<TransactionExecutionInfo, TransactionExecutionError> {
match transaction {
UserTransaction::Declare(tx, contract_class) => {
tx.try_into_executable::<T::SystemHash>(chain_id, contract_class.clone(), tx.offset_version()).and_then(
|exec| exec.execute(&mut BlockifierStateAdapter::<T>::default(), block_context, execution_config),
)
}
UserTransaction::DeployAccount(tx) => {
let executable = tx.into_executable::<T::SystemHash>(chain_id, tx.offset_version());
executable.execute(&mut BlockifierStateAdapter::<T>::default(), block_context, execution_config)
}
UserTransaction::Invoke(tx) => {
let executable = tx.into_executable::<T::SystemHash>(chain_id, tx.offset_version());
executable.execute(&mut BlockifierStateAdapter::<T>::default(), block_context, execution_config)
}
}
}
}
53 changes: 0 additions & 53 deletions crates/pallets/starknet/src/utils.rs

This file was deleted.

3 changes: 3 additions & 0 deletions crates/primitives/state/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Starknet state primitives.
#![cfg_attr(not(feature = "std"), no_std)]

#[doc(hidden)]
extern crate alloc;

use blockifier::execution::contract_class::ContractClass;
use blockifier::state::cached_state::{ContractStorageKey, StateChangesCount};
use blockifier::state::errors::StateError;
Expand Down
4 changes: 0 additions & 4 deletions crates/primitives/state/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
//! Starknet rpc state primitives.
#![cfg_attr(not(feature = "std"), no_std)]

#[doc(hidden)]
pub extern crate alloc;

use alloc::vec::Vec;

Expand Down
Loading

0 comments on commit 523ceed

Please sign in to comment.