1use crate::{
4 error::{
5 api::{FromEthApiError, FromEvmHalt},
6 ToRpcError,
7 },
8 EthApiError, RevertError,
9};
10use alloy_consensus::{BlockHeader, Transaction as _, TxType};
11use alloy_eips::eip2718::WithEncoded;
12use alloy_rpc_types_eth::{
13 simulate::{SimCallResult, SimulateError, SimulatedBlock},
14 transaction::TransactionRequest,
15 Block, BlockTransactionsKind, Header,
16};
17use jsonrpsee_types::ErrorObject;
18use reth_evm::{
19 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
20 Evm,
21};
22use reth_primitives_traits::{
23 block::BlockTx, BlockBody as _, Recovered, RecoveredBlock, SignedTransaction, TxTy,
24};
25use reth_rpc_server_types::result::rpc_err;
26use reth_rpc_types_compat::{block::from_block, TransactionCompat};
27use reth_storage_api::noop::NoopProvider;
28use revm::{
29 context_interface::result::ExecutionResult,
30 primitives::{Address, Bytes, TxKind},
31 Database,
32};
33
34#[derive(Debug, thiserror::Error)]
36pub enum EthSimulateError {
37 #[error("Block gas limit exceeded by the block's transactions")]
39 BlockGasLimitExceeded,
40 #[error("Client adjustable limit reached")]
42 GasLimitReached,
43}
44
45impl EthSimulateError {
46 const fn error_code(&self) -> i32 {
47 match self {
48 Self::BlockGasLimitExceeded => -38015,
49 Self::GasLimitReached => -38026,
50 }
51 }
52}
53
54impl ToRpcError for EthSimulateError {
55 fn to_rpc_error(&self) -> ErrorObject<'static> {
56 rpc_err(self.error_code(), self.to_string(), None)
57 }
58}
59
60#[expect(clippy::type_complexity)]
65pub fn execute_transactions<S, T>(
66 mut builder: S,
67 calls: Vec<TransactionRequest>,
68 default_gas_limit: u64,
69 chain_id: u64,
70 tx_resp_builder: &T,
71) -> Result<
72 (
73 BlockBuilderOutcome<S::Primitives>,
74 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
75 ),
76 EthApiError,
77>
78where
79 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
80 T: TransactionCompat<TxTy<S::Primitives>>,
81{
82 builder.apply_pre_execution_changes()?;
83
84 let mut results = Vec::with_capacity(calls.len());
85 for call in calls {
86 let tx = resolve_transaction(
89 call,
90 default_gas_limit,
91 builder.evm().block().basefee,
92 chain_id,
93 builder.evm_mut().db_mut(),
94 tx_resp_builder,
95 )?;
96 let tx = WithEncoded::new(Default::default(), tx);
99
100 builder
101 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
102 }
103
104 let result = builder.finish(NoopProvider::default())?;
106
107 Ok((result, results))
108}
109
110pub fn resolve_transaction<DB: Database, Tx, T: TransactionCompat<Tx>>(
115 mut tx: TransactionRequest,
116 default_gas_limit: u64,
117 block_base_fee_per_gas: u64,
118 chain_id: u64,
119 db: &mut DB,
120 tx_resp_builder: &T,
121) -> Result<Recovered<Tx>, EthApiError>
122where
123 DB::Error: Into<EthApiError>,
124{
125 let tx_type = tx.preferred_type();
128
129 let from = if let Some(from) = tx.from {
130 from
131 } else {
132 tx.from = Some(Address::ZERO);
133 Address::ZERO
134 };
135
136 if tx.nonce.is_none() {
137 tx.nonce =
138 Some(db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default());
139 }
140
141 if tx.gas.is_none() {
142 tx.gas = Some(default_gas_limit);
143 }
144
145 if tx.chain_id.is_none() {
146 tx.chain_id = Some(chain_id);
147 }
148
149 if tx.to.is_none() {
150 tx.to = Some(TxKind::Create);
151 }
152
153 if tx.buildable_type().is_none() {
155 match tx_type {
156 TxType::Legacy | TxType::Eip2930 => {
157 if tx.gas_price.is_none() {
158 tx.gas_price = Some(block_base_fee_per_gas as u128);
159 }
160 }
161 _ => {
162 if tx.max_fee_per_gas.is_none() {
164 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
165 if let Some(prio_fee) = tx.max_priority_fee_per_gas {
166 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
169 }
170 tx.max_fee_per_gas = Some(max_fee_per_gas);
171 }
172 if tx.max_priority_fee_per_gas.is_none() {
173 tx.max_priority_fee_per_gas = Some(0);
174 }
175 }
176 }
177 }
178
179 let tx = tx_resp_builder
180 .build_simulate_v1_transaction(tx)
181 .map_err(|e| EthApiError::other(e.into()))?;
182
183 Ok(Recovered::new_unchecked(tx, from))
184}
185
186#[expect(clippy::type_complexity)]
188pub fn build_simulated_block<T, B, Halt: Clone>(
189 block: RecoveredBlock<B>,
190 results: Vec<ExecutionResult<Halt>>,
191 full_transactions: bool,
192 tx_resp_builder: &T,
193) -> Result<SimulatedBlock<Block<T::Transaction, Header<B::Header>>>, T::Error>
194where
195 T: TransactionCompat<BlockTx<B>, Error: FromEthApiError + FromEvmHalt<Halt>>,
196 B: reth_primitives_traits::Block,
197{
198 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
199
200 let mut log_index = 0;
201 for (index, (result, tx)) in results.iter().zip(block.body().transactions()).enumerate() {
202 let call = match result {
203 ExecutionResult::Halt { reason, gas_used } => {
204 let error = T::Error::from_evm_halt(reason.clone(), tx.gas_limit());
205 SimCallResult {
206 return_data: Bytes::new(),
207 error: Some(SimulateError {
208 message: error.to_string(),
209 code: error.into().code(),
210 }),
211 gas_used: *gas_used,
212 logs: Vec::new(),
213 status: false,
214 }
215 }
216 ExecutionResult::Revert { output, gas_used } => {
217 let error = RevertError::new(output.clone());
218 SimCallResult {
219 return_data: output.clone(),
220 error: Some(SimulateError {
221 code: error.error_code(),
222 message: error.to_string(),
223 }),
224 gas_used: *gas_used,
225 status: false,
226 logs: Vec::new(),
227 }
228 }
229 ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
230 return_data: output.clone().into_data(),
231 error: None,
232 gas_used: *gas_used,
233 logs: logs
234 .iter()
235 .map(|log| {
236 log_index += 1;
237 alloy_rpc_types_eth::Log {
238 inner: log.clone(),
239 log_index: Some(log_index - 1),
240 transaction_index: Some(index as u64),
241 transaction_hash: Some(*tx.tx_hash()),
242 block_number: Some(block.header().number()),
243 block_timestamp: Some(block.header().timestamp()),
244 ..Default::default()
245 }
246 })
247 .collect(),
248 status: true,
249 },
250 };
251
252 calls.push(call);
253 }
254
255 let txs_kind =
256 if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes };
257
258 let block = from_block(block, txs_kind, tx_resp_builder)?;
259 Ok(SimulatedBlock { inner: block, calls })
260}