Skip to content

Commit 12b1c26

Browse files
starknet_os_runner: virtual block executor
1 parent f0e2271 commit 12b1c26

File tree

2 files changed

+166
-129
lines changed

2 files changed

+166
-129
lines changed

crates/starknet_os_runner/src/virtual_block_executor.rs

Lines changed: 111 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ use blockifier::blockifier::transaction_executor::{
66
use blockifier::context::BlockContext;
77
use blockifier::state::cached_state::{CachedState, StateMaps};
88
use blockifier::state::contract_class_manager::ContractClassManager;
9-
use blockifier::state::state_reader_and_contract_manager::StateReaderAndContractManager;
9+
use blockifier::state::state_reader_and_contract_manager::{
10+
FetchCompiledClasses,
11+
StateReaderAndContractManager,
12+
};
1013
use blockifier::transaction::account_transaction::ExecutionFlags;
1114
use blockifier::transaction::transaction_execution::Transaction as BlockifierTransaction;
1215
use blockifier_reexecution::state_reader::rpc_state_reader::RpcStateReader;
1316
use starknet_api::block::BlockNumber;
1417
use starknet_api::core::ChainId;
18+
use starknet_api::transaction::fields::Fee;
1519
use starknet_api::transaction::{Transaction, TransactionHash};
1620

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

3337
/// Executes a virtual block of transactions.
3438
///
35-
/// A virtual block executor runs transactions without block preprocessing
36-
/// (`pre_process_block`), which is useful for simulating execution or generating
39+
/// A virtual block executor runs transactions on top of a given, finalized block.
40+
/// This means that some parts, like block preprocessing
41+
/// (`pre_process_block`), are skipped. Useful for simulating execution or generating
3742
/// OS input for proving.
3843
///
3944
/// Implementations can fetch state from different sources (RPC nodes, local state,
@@ -48,12 +53,11 @@ pub struct VirtualBlockExecutionData {
4853
///
4954
/// ```text
5055
/// let executor = RpcVirtualBlockExecutor::new(
51-
/// "http://localhost:9545".to_string(),
52-
/// ChainId::Mainnet,
53-
/// contract_class_manager,
56+
/// node_url: "http://localhost:9545".to_string(),
57+
/// chain_id: ChainId::Mainnet,
58+
/// block_number: BlockNumber(1000),
5459
/// );
55-
///
56-
/// let execution_data = executor.execute(block_number, transactions)?;
60+
/// let execution_data = executor.execute(block_number, contract_class_manager, transactions)?;
5761
/// // Use execution_data to build OS input for proving...
5862
/// ```
5963
pub trait VirtualBlockExecutor {
@@ -62,6 +66,7 @@ pub trait VirtualBlockExecutor {
6266
/// # Arguments
6367
///
6468
/// * `block_number` - The block number to use for state and context
69+
/// * `contract_class_manager` - Manager for compiled contract classes
6570
/// * `txs` - Invoke transactions to execute (with their hashes)
6671
///
6772
/// # Returns
@@ -71,105 +76,16 @@ pub trait VirtualBlockExecutor {
7176
fn execute(
7277
&self,
7378
block_number: BlockNumber,
79+
contract_class_manager: ContractClassManager,
7480
txs: Vec<(Transaction, TransactionHash)>,
7581
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError> {
7682
let blockifier_txs = Self::convert_invoke_txs(txs)?;
77-
self.execute_inner(block_number, blockifier_txs)
78-
}
79-
80-
fn execute_inner(
81-
&self,
82-
block_number: BlockNumber,
83-
txs: Vec<BlockifierTransaction>,
84-
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError>;
85-
86-
/// Converts Invoke transactions to blockifier transactions.
87-
///
88-
/// Uses execution flags that skip fee charging and nonce check.
89-
/// Returns an error if any transaction is not an Invoke.
90-
fn convert_invoke_txs(
91-
txs: Vec<(Transaction, TransactionHash)>,
92-
) -> Result<Vec<BlockifierTransaction>, VirtualBlockExecutorError> {
93-
// Skip validation, fee charging, and nonce check for virtual block execution.
94-
let execution_flags = ExecutionFlags {
95-
validate: true,
96-
charge_fee: false,
97-
strict_nonce_check: false,
98-
only_query: false,
99-
};
100-
101-
txs.into_iter()
102-
.map(|(tx, tx_hash)| {
103-
if !matches!(tx, Transaction::Invoke(_)) {
104-
return Err(VirtualBlockExecutorError::UnsupportedTransactionType);
105-
}
106-
107-
BlockifierTransaction::from_api(
108-
tx,
109-
tx_hash,
110-
None, // class_info - not needed for Invoke.
111-
None, // paid_fee_on_l1 - not needed for Invoke.
112-
None, // deployed_contract_address - not needed for Invoke.
113-
execution_flags.clone(),
114-
)
115-
.map_err(|e| VirtualBlockExecutorError::TransactionExecutionError(e.to_string()))
116-
})
117-
.collect()
118-
}
119-
}
120-
121-
/// RPC-based virtual block executor.
122-
///
123-
/// This executor fetches historical state from an RPC node and executes transactions
124-
/// without block preprocessing. Validation and fee charging are always skipped,
125-
/// making it suitable for simulation and OS input generation.
126-
pub struct RpcVirtualBlockExecutor {
127-
node_url: String,
128-
chain_id: ChainId,
129-
contract_class_manager: ContractClassManager,
130-
}
131-
132-
impl RpcVirtualBlockExecutor {
133-
/// Creates a new RPC-based virtual block executor.
134-
///
135-
/// # Arguments
136-
///
137-
/// * `node_url` - URL of the RPC node to fetch state from
138-
/// * `chain_id` - The chain ID for transaction hash computation
139-
/// * `contract_class_manager` - Manager for compiled contract classes
140-
pub fn new(
141-
node_url: String,
142-
chain_id: ChainId,
143-
contract_class_manager: ContractClassManager,
144-
) -> Self {
145-
Self { node_url, chain_id, contract_class_manager }
146-
}
147-
}
148-
149-
impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
150-
fn execute_inner(
151-
&self,
152-
block_number: BlockNumber,
153-
txs: Vec<BlockifierTransaction>,
154-
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError> {
155-
// Create RPC state reader for the given block.
156-
let rpc_state_reader = RpcStateReader::new_with_config_from_url(
157-
self.node_url.clone(),
158-
self.chain_id.clone(),
159-
block_number,
160-
);
161-
162-
// Get block context from RPC.
163-
let block_context = rpc_state_reader
164-
.get_block_context()
165-
.map_err(|e| VirtualBlockExecutorError::ReexecutionError(Box::new(e)))?;
83+
let block_context = self.block_context(block_number)?;
84+
let state_reader = self.state_reader(block_number)?;
16685

16786
// Create state reader with contract manager.
168-
let state_reader_and_contract_manager = StateReaderAndContractManager::new(
169-
rpc_state_reader,
170-
self.contract_class_manager.clone(),
171-
None,
172-
);
87+
let state_reader_and_contract_manager =
88+
StateReaderAndContractManager::new(state_reader, contract_class_manager, None);
17389

17490
let block_state = CachedState::new(state_reader_and_contract_manager);
17591

@@ -181,7 +97,7 @@ impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
18197
);
18298

18399
// Execute all transactions.
184-
let execution_results = transaction_executor.execute_txs(&txs, None);
100+
let execution_results = transaction_executor.execute_txs(&blockifier_txs, None);
185101

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

204120
Ok(VirtualBlockExecutionData { execution_outputs, block_context, initial_reads })
205121
}
122+
123+
/// Converts Invoke transactions to blockifier transactions.
124+
///
125+
/// Uses execution flags that skip fee charging and nonce check.
126+
/// Returns an error if any transaction is not an Invoke.
127+
fn convert_invoke_txs(
128+
txs: Vec<(Transaction, TransactionHash)>,
129+
) -> Result<Vec<BlockifierTransaction>, VirtualBlockExecutorError> {
130+
txs.into_iter()
131+
.map(|(tx, tx_hash)| {
132+
if let Transaction::Invoke(invoke_tx) = tx {
133+
// Execute with validation, conditional fee charging based on resource bounds,
134+
// but skip strict nonce check for virtual block execution.
135+
let execution_flags = ExecutionFlags {
136+
only_query: false,
137+
charge_fee: invoke_tx.resource_bounds().max_possible_fee(invoke_tx.tip())
138+
> Fee(0),
139+
validate: true,
140+
strict_nonce_check: false,
141+
};
142+
143+
BlockifierTransaction::from_api(
144+
Transaction::Invoke(invoke_tx),
145+
tx_hash,
146+
None, // class_info - not needed for Invoke.
147+
None, // paid_fee_on_l1 - not needed for Invoke.
148+
None, // deployed_contract_address - not needed for Invoke.
149+
execution_flags,
150+
)
151+
.map_err(|e| {
152+
VirtualBlockExecutorError::TransactionExecutionError(e.to_string())
153+
})
154+
} else {
155+
Err(VirtualBlockExecutorError::UnsupportedTransactionType)
156+
}
157+
})
158+
.collect()
159+
}
160+
/// Returns the block context for the given block number.
161+
fn block_context(
162+
&self,
163+
block_number: BlockNumber,
164+
) -> Result<BlockContext, VirtualBlockExecutorError>;
165+
166+
/// Returns a state reader that implements `FetchCompiledClasses` for the given block number.
167+
/// Must be `Send + Sync + 'static` to be used in the transaction executor.
168+
fn state_reader(
169+
&self,
170+
block_number: BlockNumber,
171+
) -> Result<impl FetchCompiledClasses + Send + Sync + 'static, VirtualBlockExecutorError>;
172+
}
173+
174+
#[allow(dead_code)]
175+
pub(crate) struct RpcVirtualBlockExecutor {
176+
pub rpc_state_reader: RpcStateReader,
177+
}
178+
179+
impl RpcVirtualBlockExecutor {
180+
#[allow(dead_code)]
181+
pub fn new(node_url: String, chain_id: ChainId, block_number: BlockNumber) -> Self {
182+
Self {
183+
rpc_state_reader: RpcStateReader::new_with_config_from_url(
184+
node_url,
185+
chain_id,
186+
block_number,
187+
),
188+
}
189+
}
190+
}
191+
192+
/// RPC-based virtual block executor.
193+
///
194+
/// This executor fetches historical state from an RPC node and executes transactions
195+
/// without block preprocessing. Validation and fee charging are always skipped,
196+
/// making it suitable for simulation and OS input generation.
197+
impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
198+
fn block_context(
199+
&self,
200+
_block_number: BlockNumber,
201+
) -> Result<BlockContext, VirtualBlockExecutorError> {
202+
self.rpc_state_reader
203+
.get_block_context()
204+
.map_err(|e| VirtualBlockExecutorError::ReexecutionError(Box::new(e)))
205+
}
206+
207+
fn state_reader(
208+
&self,
209+
_block_number: BlockNumber,
210+
) -> Result<impl FetchCompiledClasses + Send + Sync + 'static, VirtualBlockExecutorError> {
211+
// Clone the RpcStateReader to avoid lifetime issues ( not a big struct).
212+
Ok(self.rpc_state_reader.clone())
213+
}
206214
}

0 commit comments

Comments
 (0)