Skip to content
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
214 changes: 111 additions & 103 deletions crates/starknet_os_runner/src/virtual_block_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ use blockifier::blockifier::transaction_executor::{
use blockifier::context::BlockContext;
use blockifier::state::cached_state::{CachedState, StateMaps};
use blockifier::state::contract_class_manager::ContractClassManager;
use blockifier::state::state_reader_and_contract_manager::StateReaderAndContractManager;
use blockifier::state::state_reader_and_contract_manager::{
FetchCompiledClasses,
StateReaderAndContractManager,
};
use blockifier::transaction::account_transaction::ExecutionFlags;
use blockifier::transaction::transaction_execution::Transaction as BlockifierTransaction;
use blockifier_reexecution::state_reader::rpc_state_reader::RpcStateReader;
use starknet_api::block::BlockNumber;
use starknet_api::core::ChainId;
use starknet_api::transaction::fields::Fee;
use starknet_api::transaction::{Transaction, TransactionHash};

use crate::errors::VirtualBlockExecutorError;
Expand All @@ -32,8 +36,9 @@ pub struct VirtualBlockExecutionData {

/// Executes a virtual block of transactions.
///
/// A virtual block executor runs transactions without block preprocessing
/// (`pre_process_block`), which is useful for simulating execution or generating
/// A virtual block executor runs transactions on top of a given, finalized block.
/// This means that some parts, like block preprocessing
/// (`pre_process_block`), are skipped. Useful for simulating execution or generating
/// OS input for proving.
///
/// Implementations can fetch state from different sources (RPC nodes, local state,
Expand All @@ -48,12 +53,11 @@ pub struct VirtualBlockExecutionData {
///
/// ```text
/// let executor = RpcVirtualBlockExecutor::new(
/// "http://localhost:9545".to_string(),
/// ChainId::Mainnet,
/// contract_class_manager,
/// node_url: "http://localhost:9545".to_string(),
/// chain_id: ChainId::Mainnet,
/// block_number: BlockNumber(1000),
/// );
///
/// let execution_data = executor.execute(block_number, transactions)?;
/// let execution_data = executor.execute(block_number, contract_class_manager, transactions)?;
/// // Use execution_data to build OS input for proving...
/// ```
pub trait VirtualBlockExecutor {
Expand All @@ -62,6 +66,7 @@ pub trait VirtualBlockExecutor {
/// # Arguments
///
/// * `block_number` - The block number to use for state and context
/// * `contract_class_manager` - Manager for compiled contract classes
/// * `txs` - Invoke transactions to execute (with their hashes)
///
/// # Returns
Expand All @@ -71,105 +76,16 @@ pub trait VirtualBlockExecutor {
fn execute(
&self,
block_number: BlockNumber,
contract_class_manager: ContractClassManager,
txs: Vec<(Transaction, TransactionHash)>,
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError> {
let blockifier_txs = Self::convert_invoke_txs(txs)?;
self.execute_inner(block_number, blockifier_txs)
}

fn execute_inner(
&self,
block_number: BlockNumber,
txs: Vec<BlockifierTransaction>,
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError>;

/// Converts Invoke transactions to blockifier transactions.
///
/// Uses execution flags that skip fee charging and nonce check.
/// Returns an error if any transaction is not an Invoke.
fn convert_invoke_txs(
txs: Vec<(Transaction, TransactionHash)>,
) -> Result<Vec<BlockifierTransaction>, VirtualBlockExecutorError> {
// Skip validation, fee charging, and nonce check for virtual block execution.
let execution_flags = ExecutionFlags {
validate: true,
charge_fee: false,
strict_nonce_check: false,
only_query: false,
};

txs.into_iter()
.map(|(tx, tx_hash)| {
if !matches!(tx, Transaction::Invoke(_)) {
return Err(VirtualBlockExecutorError::UnsupportedTransactionType);
}

BlockifierTransaction::from_api(
tx,
tx_hash,
None, // class_info - not needed for Invoke.
None, // paid_fee_on_l1 - not needed for Invoke.
None, // deployed_contract_address - not needed for Invoke.
execution_flags.clone(),
)
.map_err(|e| VirtualBlockExecutorError::TransactionExecutionError(e.to_string()))
})
.collect()
}
}

/// RPC-based virtual block executor.
///
/// This executor fetches historical state from an RPC node and executes transactions
/// without block preprocessing. Validation and fee charging are always skipped,
/// making it suitable for simulation and OS input generation.
pub struct RpcVirtualBlockExecutor {
node_url: String,
chain_id: ChainId,
contract_class_manager: ContractClassManager,
}

impl RpcVirtualBlockExecutor {
/// Creates a new RPC-based virtual block executor.
///
/// # Arguments
///
/// * `node_url` - URL of the RPC node to fetch state from
/// * `chain_id` - The chain ID for transaction hash computation
/// * `contract_class_manager` - Manager for compiled contract classes
pub fn new(
node_url: String,
chain_id: ChainId,
contract_class_manager: ContractClassManager,
) -> Self {
Self { node_url, chain_id, contract_class_manager }
}
}

impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
fn execute_inner(
&self,
block_number: BlockNumber,
txs: Vec<BlockifierTransaction>,
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError> {
// Create RPC state reader for the given block.
let rpc_state_reader = RpcStateReader::new_with_config_from_url(
self.node_url.clone(),
self.chain_id.clone(),
block_number,
);

// Get block context from RPC.
let block_context = rpc_state_reader
.get_block_context()
.map_err(|e| VirtualBlockExecutorError::ReexecutionError(Box::new(e)))?;
let block_context = self.block_context(block_number)?;
let state_reader = self.state_reader(block_number)?;

// Create state reader with contract manager.
let state_reader_and_contract_manager = StateReaderAndContractManager::new(
rpc_state_reader,
self.contract_class_manager.clone(),
None,
);
let state_reader_and_contract_manager =
StateReaderAndContractManager::new(state_reader, contract_class_manager, None);

let block_state = CachedState::new(state_reader_and_contract_manager);

Expand All @@ -181,7 +97,7 @@ impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
);

// Execute all transactions.
let execution_results = transaction_executor.execute_txs(&txs, None);
let execution_results = transaction_executor.execute_txs(&blockifier_txs, None);

// Collect results, returning error if any transaction fails.
let execution_outputs: Vec<TransactionExecutionOutput> = execution_results
Expand All @@ -203,4 +119,96 @@ impl VirtualBlockExecutor for RpcVirtualBlockExecutor {

Ok(VirtualBlockExecutionData { execution_outputs, block_context, initial_reads })
}

/// Converts Invoke transactions to blockifier transactions.
///
/// Uses execution flags that skip fee charging and nonce check.
/// Returns an error if any transaction is not an Invoke.
fn convert_invoke_txs(
txs: Vec<(Transaction, TransactionHash)>,
) -> Result<Vec<BlockifierTransaction>, VirtualBlockExecutorError> {
txs.into_iter()
.map(|(tx, tx_hash)| {
if let Transaction::Invoke(invoke_tx) = tx {
// Execute with validation, conditional fee charging based on resource bounds,
// but skip strict nonce check for virtual block execution.
let execution_flags = ExecutionFlags {
only_query: false,
charge_fee: invoke_tx.resource_bounds().max_possible_fee(invoke_tx.tip())
> Fee(0),
validate: true,
strict_nonce_check: false,
};

BlockifierTransaction::from_api(
Transaction::Invoke(invoke_tx),
tx_hash,
None, // class_info - not needed for Invoke.
None, // paid_fee_on_l1 - not needed for Invoke.
None, // deployed_contract_address - not needed for Invoke.
execution_flags,
)
.map_err(|e| {
VirtualBlockExecutorError::TransactionExecutionError(e.to_string())
})
} else {
Err(VirtualBlockExecutorError::UnsupportedTransactionType)
}
})
.collect()
}
/// Returns the block context for the given block number.
fn block_context(
&self,
block_number: BlockNumber,
) -> Result<BlockContext, VirtualBlockExecutorError>;

/// Returns a state reader that implements `FetchCompiledClasses` for the given block number.
/// Must be `Send + Sync + 'static` to be used in the transaction executor.
fn state_reader(
&self,
block_number: BlockNumber,
) -> Result<impl FetchCompiledClasses + Send + Sync + 'static, VirtualBlockExecutorError>;
}

#[allow(dead_code)]
pub(crate) struct RpcVirtualBlockExecutor {
pub rpc_state_reader: RpcStateReader,
}

impl RpcVirtualBlockExecutor {
#[allow(dead_code)]
pub fn new(node_url: String, chain_id: ChainId, block_number: BlockNumber) -> Self {
Self {
rpc_state_reader: RpcStateReader::new_with_config_from_url(
node_url,
chain_id,
block_number,
),
}
}
}

/// RPC-based virtual block executor.
///
/// This executor fetches historical state from an RPC node and executes transactions
/// without block preprocessing. Validation and fee charging are always skipped,
/// making it suitable for simulation and OS input generation.
impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
fn block_context(
&self,
_block_number: BlockNumber,
) -> Result<BlockContext, VirtualBlockExecutorError> {
self.rpc_state_reader
.get_block_context()
.map_err(|e| VirtualBlockExecutorError::ReexecutionError(Box::new(e)))
}

fn state_reader(
&self,
_block_number: BlockNumber,
) -> Result<impl FetchCompiledClasses + Send + Sync + 'static, VirtualBlockExecutorError> {
// Clone the RpcStateReader to avoid lifetime issues ( not a big struct).
Ok(self.rpc_state_reader.clone())
}
}
Loading