Skip to content

Commit af51ccc

Browse files
starknet_os_runner: virtual block executor
1 parent c58583b commit af51ccc

File tree

6 files changed

+248
-1
lines changed

6 files changed

+248
-1
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/blockifier_reexecution/src/state_reader/rpc_state_reader.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ impl RpcStateReader {
201201
}
202202
}
203203

204+
/// Creates an RpcStateReader from a node URL, chain ID, and block number.
205+
pub fn new_with_default_config(
206+
node_url: String,
207+
chain_id: ChainId,
208+
block_number: BlockNumber,
209+
) -> Self {
210+
let config = RpcStateReaderConfig::from_url(node_url);
211+
Self::new(&config, chain_id, block_number, false)
212+
}
213+
204214
pub fn new_for_testing(block_number: BlockNumber) -> Self {
205215
RpcStateReader::new(&get_rpc_state_reader_config(), ChainId::Mainnet, block_number, false)
206216
}

crates/starknet_os_runner/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ license-file.workspace = true
77
description = "Runs transactions through the Starknet OS and returns Cairo PIE and OS output."
88

99
[dependencies]
10+
blockifier.workspace = true
11+
blockifier_reexecution.workspace = true
12+
starknet_api.workspace = true
13+
thiserror.workspace = true
1014

1115
[lints]
1216
workspace = true
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use blockifier_reexecution::errors::ReexecutionError;
2+
use thiserror::Error;
3+
4+
#[derive(Debug, Error)]
5+
pub enum VirtualBlockExecutorError {
6+
#[error(transparent)]
7+
// Boxed to reduce the size of Result on the stack (ReexecutionError is >128 bytes).
8+
ReexecutionError(#[from] Box<ReexecutionError>),
9+
10+
#[error("Transaction execution failed: {0}")]
11+
TransactionExecutionError(String),
12+
13+
#[error("Block state unavailable after execution")]
14+
StateUnavailable,
15+
16+
#[error("Unsupported transaction type: only Invoke transactions are supported")]
17+
UnsupportedTransactionType,
18+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
//! Starknet OS Runner - executes transactions through the OS and returns Cairo PIE and output.
1+
pub mod errors;
2+
pub mod virtual_block_executor;
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
use blockifier::blockifier::config::TransactionExecutorConfig;
2+
use blockifier::blockifier::transaction_executor::{
3+
TransactionExecutionOutput,
4+
TransactionExecutor,
5+
};
6+
use blockifier::context::BlockContext;
7+
use blockifier::state::cached_state::{CachedState, StateMaps};
8+
use blockifier::state::contract_class_manager::ContractClassManager;
9+
use blockifier::state::state_reader_and_contract_manager::StateReaderAndContractManager;
10+
use blockifier::transaction::account_transaction::ExecutionFlags;
11+
use blockifier::transaction::transaction_execution::Transaction as BlockifierTransaction;
12+
use blockifier_reexecution::state_reader::rpc_state_reader::RpcStateReader;
13+
use starknet_api::block::BlockNumber;
14+
use starknet_api::core::ChainId;
15+
use starknet_api::transaction::{Transaction, TransactionHash};
16+
17+
use crate::errors::VirtualBlockExecutorError;
18+
19+
/// Captures execution data for a virtual block (multiple transactions).
20+
///
21+
/// A virtual block is a set of transactions executed together without block preprocessing,
22+
/// useful for OS input generation and proving. This struct contains all the execution
23+
/// outputs, block context, and initial state reads needed for proof generation.
24+
pub struct VirtualBlockExecutionData {
25+
/// Execution outputs for all transactions in the virtual block.
26+
pub execution_outputs: Vec<TransactionExecutionOutput>,
27+
/// The block context in which the transactions were executed.
28+
pub block_context: BlockContext,
29+
/// The initial state reads (accessed state) during execution.
30+
pub initial_reads: StateMaps,
31+
}
32+
33+
/// Executes a virtual block of transactions.
34+
///
35+
/// A virtual block executor runs transactions without block preprocessing
36+
/// (`pre_process_block`), which is useful for simulating execution or generating
37+
/// OS input for proving.
38+
///
39+
/// Implementations can fetch state from different sources (RPC nodes, local state,
40+
/// mocked data, etc.).
41+
///
42+
/// # Note
43+
///
44+
/// - Currently only Invoke transactions are supported.
45+
/// - Validation and fee charging are always skipped (useful for simulation/proving).
46+
///
47+
/// # Examples
48+
///
49+
/// ```text
50+
/// let executor = RpcVirtualBlockExecutor::new(
51+
/// "http://localhost:9545".to_string(),
52+
/// ChainId::Mainnet,
53+
/// contract_class_manager,
54+
/// );
55+
///
56+
/// let execution_data = executor.execute(block_number, transactions)?;
57+
/// // Use execution_data to build OS input for proving...
58+
/// ```
59+
pub trait VirtualBlockExecutor {
60+
/// Executes a virtual block at the given block number.
61+
///
62+
/// # Arguments
63+
///
64+
/// * `block_number` - The block number to use for state and context
65+
/// * `txs` - Invoke transactions to execute (with their hashes)
66+
///
67+
/// # Returns
68+
///
69+
/// Returns `VirtualBlockExecutionData` containing execution outputs for all
70+
/// transactions, or an error if any transaction fails or is not an Invoke.
71+
fn execute(
72+
&self,
73+
block_number: BlockNumber,
74+
txs: Vec<(Transaction, TransactionHash)>,
75+
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError>{
76+
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+
150+
impl VirtualBlockExecutor for RpcVirtualBlockExecutor {
151+
fn execute_inner(
152+
&self,
153+
block_number: BlockNumber,
154+
txs: Vec<BlockifierTransaction>
155+
) -> Result<VirtualBlockExecutionData, VirtualBlockExecutorError> {
156+
157+
// Create RPC state reader for the given block.
158+
let rpc_state_reader = RpcStateReader::new_with_default_config(
159+
self.node_url.clone(),
160+
self.chain_id.clone(),
161+
block_number,
162+
);
163+
164+
// Get block context from RPC.
165+
let block_context = rpc_state_reader
166+
.get_block_context()
167+
.map_err(|e| VirtualBlockExecutorError::ReexecutionError(Box::new(e)))?;
168+
169+
// Create state reader with contract manager.
170+
let state_reader_and_contract_manager = StateReaderAndContractManager::new(
171+
rpc_state_reader,
172+
self.contract_class_manager.clone(),
173+
None,
174+
);
175+
176+
let block_state = CachedState::new(state_reader_and_contract_manager);
177+
178+
// Create executor WITHOUT preprocessing (no pre_process_block call).
179+
let mut transaction_executor = TransactionExecutor::new(
180+
block_state,
181+
block_context.clone(),
182+
TransactionExecutorConfig::default(),
183+
);
184+
185+
// Execute all transactions.
186+
let execution_results = transaction_executor.execute_txs(&txs, None);
187+
188+
// Collect results, returning error if any transaction fails.
189+
let execution_outputs: Vec<TransactionExecutionOutput> = execution_results
190+
.into_iter()
191+
.map(|result| {
192+
result.map_err(|e| {
193+
VirtualBlockExecutorError::TransactionExecutionError(e.to_string())
194+
})
195+
})
196+
.collect::<Result<Vec<_>, _>>()?;
197+
198+
// Get initial state reads.
199+
let initial_reads = transaction_executor
200+
.block_state
201+
.as_ref()
202+
.ok_or(VirtualBlockExecutorError::StateUnavailable)?
203+
.get_initial_reads()
204+
.map_err(|e| VirtualBlockExecutorError::ReexecutionError(Box::new(e.into())))?;
205+
206+
Ok(VirtualBlockExecutionData { execution_outputs, block_context, initial_reads })
207+
}
208+
}

0 commit comments

Comments
 (0)