1use crate::{
4 error::{
5 api::{FromEthApiError, FromEvmHalt},
6 ToRpcError,
7 },
8 EthApiError, RevertError,
9};
10use alloy_consensus::{BlockHeader, Transaction as _};
11use alloy_eips::eip2718::WithEncoded;
12use alloy_network::TransactionBuilder;
13use alloy_rpc_types_eth::{
14 simulate::{SimCallResult, SimulateError, SimulatedBlock},
15 BlockTransactionsKind,
16};
17use jsonrpsee_types::ErrorObject;
18use reth_evm::{
19 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
20 Evm,
21};
22use reth_primitives_traits::{
23 BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction,
24};
25use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq};
26use reth_rpc_server_types::result::rpc_err;
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)]
67pub fn execute_transactions<S, T>(
68 mut builder: S,
69 calls: Vec<RpcTxReq<T::Network>>,
70 default_gas_limit: u64,
71 chain_id: u64,
72 tx_resp_builder: &T,
73) -> Result<
74 (
75 BlockBuilderOutcome<S::Primitives>,
76 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
77 ),
78 EthApiError,
79>
80where
81 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
82 T: RpcConvert<Primitives = S::Primitives>,
83{
84 builder.apply_pre_execution_changes()?;
85
86 let mut results = Vec::with_capacity(calls.len());
87 for call in calls {
88 let tx = resolve_transaction(
91 call,
92 default_gas_limit,
93 builder.evm().block().basefee,
94 chain_id,
95 builder.evm_mut().db_mut(),
96 tx_resp_builder,
97 )?;
98 let tx = WithEncoded::new(Default::default(), tx);
101
102 builder
103 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
104 }
105
106 let result = builder.finish(NoopProvider::default())?;
108
109 Ok((result, results))
110}
111
112pub fn resolve_transaction<DB: Database, Tx, T>(
119 mut tx: RpcTxReq<T::Network>,
120 default_gas_limit: u64,
121 block_base_fee_per_gas: u64,
122 chain_id: u64,
123 db: &mut DB,
124 tx_resp_builder: &T,
125) -> Result<Recovered<Tx>, EthApiError>
126where
127 DB::Error: Into<EthApiError>,
128 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
129{
130 let tx_type = tx.as_ref().output_tx_type();
133
134 let from = if let Some(from) = tx.as_ref().from() {
135 from
136 } else {
137 tx.as_mut().set_from(Address::ZERO);
138 Address::ZERO
139 };
140
141 if tx.as_ref().nonce().is_none() {
142 tx.as_mut().set_nonce(
143 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
144 );
145 }
146
147 if tx.as_ref().gas_limit().is_none() {
148 tx.as_mut().set_gas_limit(default_gas_limit);
149 }
150
151 if tx.as_ref().chain_id().is_none() {
152 tx.as_mut().set_chain_id(chain_id);
153 }
154
155 if tx.as_ref().kind().is_none() {
156 tx.as_mut().set_kind(TxKind::Create);
157 }
158
159 if tx.as_ref().output_tx_type_checked().is_none() {
161 if tx_type.is_legacy() || tx_type.is_eip2930() {
162 if tx.as_ref().gas_price().is_none() {
163 tx.as_mut().set_gas_price(block_base_fee_per_gas as u128);
164 }
165 } else {
166 if tx.as_ref().max_fee_per_gas().is_none() {
168 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
169 if let Some(prio_fee) = tx.as_ref().max_priority_fee_per_gas() {
170 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
173 }
174 tx.as_mut().set_max_fee_per_gas(max_fee_per_gas);
175 }
176 if tx.as_ref().max_priority_fee_per_gas().is_none() {
177 tx.as_mut().set_max_priority_fee_per_gas(0);
178 }
179 }
180 }
181
182 let tx = tx_resp_builder
183 .build_simulate_v1_transaction(tx)
184 .map_err(|e| EthApiError::other(e.into()))?;
185
186 Ok(Recovered::new_unchecked(tx, from))
187}
188
189pub fn build_simulated_block<T, Halt: Clone>(
191 block: RecoveredBlock<BlockTy<T::Primitives>>,
192 results: Vec<ExecutionResult<Halt>>,
193 txs_kind: BlockTransactionsKind,
194 tx_resp_builder: &T,
195) -> Result<SimulatedBlock<RpcBlock<T::Network>>, T::Error>
196where
197 T: RpcConvert<Error: FromEthApiError + FromEvmHalt<Halt>>,
198{
199 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
200
201 let mut log_index = 0;
202 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
203 let call = match result {
204 ExecutionResult::Halt { reason, gas_used } => {
205 let error = T::Error::from_evm_halt(reason, tx.gas_limit());
206 SimCallResult {
207 return_data: Bytes::new(),
208 error: Some(SimulateError {
209 message: error.to_string(),
210 code: error.into().code(),
211 }),
212 gas_used,
213 logs: Vec::new(),
214 status: false,
215 }
216 }
217 ExecutionResult::Revert { output, gas_used } => {
218 let error = RevertError::new(output.clone());
219 SimCallResult {
220 return_data: output,
221 error: Some(SimulateError {
222 code: error.error_code(),
223 message: error.to_string(),
224 }),
225 gas_used,
226 status: false,
227 logs: Vec::new(),
228 }
229 }
230 ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
231 return_data: output.into_data(),
232 error: None,
233 gas_used,
234 logs: logs
235 .into_iter()
236 .map(|log| {
237 log_index += 1;
238 alloy_rpc_types_eth::Log {
239 inner: log,
240 log_index: Some(log_index - 1),
241 transaction_index: Some(index as u64),
242 transaction_hash: Some(*tx.tx_hash()),
243 block_number: Some(block.header().number()),
244 block_timestamp: Some(block.header().timestamp()),
245 ..Default::default()
246 }
247 })
248 .collect(),
249 status: true,
250 },
251 };
252
253 calls.push(call);
254 }
255
256 let block = block.into_rpc_block(
257 txs_kind,
258 |tx, tx_info| tx_resp_builder.fill(tx, tx_info),
259 |header, size| tx_resp_builder.convert_header(header, size),
260 )?;
261 Ok(SimulatedBlock { inner: block, calls })
262}