1use std::sync::Arc;
20
21use ethereum::TransactionV3 as EthereumTransaction;
22use ethereum_types::{H256, U256, U64};
23use jsonrpsee::core::RpcResult;
24use sc_client_api::backend::{Backend, StorageProvider};
26use sc_transaction_pool_api::{InPoolTransaction, TransactionPool};
27use sp_api::{ApiExt, ProvideRuntimeApi};
28use sp_blockchain::HeaderBackend;
29use sp_core::hashing::keccak_256;
30use sp_runtime::traits::Block as BlockT;
31use fc_rpc_core::types::*;
33use fp_rpc::EthereumRuntimeRPCApi;
34
35use crate::{
36 eth::{transaction_build, BlockInfo, Eth},
37 frontier_backend_client, internal_err,
38};
39
40impl<B, C, P, CT, BE, CIDP, EC> Eth<B, C, P, CT, BE, CIDP, EC>
41where
42 B: BlockT,
43 C: ProvideRuntimeApi<B>,
44 C::Api: EthereumRuntimeRPCApi<B>,
45 C: HeaderBackend<B> + StorageProvider<B, BE> + 'static,
46 BE: Backend<B> + 'static,
47 P: TransactionPool<Block = B, Hash = B::Hash> + 'static,
48{
49 pub async fn transaction_by_hash(&self, hash: H256) -> RpcResult<Option<Transaction>> {
50 let client = Arc::clone(&self.client);
51 let backend = Arc::clone(&self.backend);
52 let graph = Arc::clone(&self.graph);
53
54 let (eth_block_hash, index) = match frontier_backend_client::load_transactions::<B, C>(
55 client.as_ref(),
56 backend.as_ref(),
57 hash,
58 true,
59 )
60 .await
61 .map_err(|err| internal_err(format!("{err:?}")))?
62 {
63 Some((eth_block_hash, index)) => (eth_block_hash, index as usize),
64 None => {
65 let api = client.runtime_api();
66 let best_block = client.info().best_hash;
67
68 let api_version = if let Ok(Some(api_version)) =
69 api.api_version::<dyn EthereumRuntimeRPCApi<B>>(best_block)
70 {
71 api_version
72 } else {
73 return Err(internal_err("failed to retrieve Runtime Api version"));
74 };
75 let mut xts: Vec<<B as BlockT>::Extrinsic> = Vec::new();
78 xts.extend(
80 graph
81 .ready()
82 .map(|in_pool_tx| in_pool_tx.data().as_ref().clone())
83 .collect::<Vec<<B as BlockT>::Extrinsic>>(),
84 );
85
86 xts.extend(
88 graph
89 .futures()
90 .iter()
91 .map(|in_pool_tx| in_pool_tx.data().as_ref().clone())
92 .collect::<Vec<<B as BlockT>::Extrinsic>>(),
93 );
94
95 let ethereum_transactions: Vec<EthereumTransaction> = if api_version > 1 {
96 api.extrinsic_filter(best_block, xts).map_err(|err| {
97 internal_err(format!("fetch runtime extrinsic filter failed: {err:?}"))
98 })?
99 } else {
100 #[allow(deprecated)]
101 let legacy = api.extrinsic_filter_before_version_2(best_block, xts)
102 .map_err(|err| {
103 internal_err(format!("fetch runtime extrinsic filter failed: {err:?}"))
104 })?;
105 legacy.into_iter().map(|tx| tx.into()).collect()
106 };
107
108 for txn in ethereum_transactions {
109 let inner_hash = txn.hash();
110 if hash == inner_hash {
111 return Ok(Some(transaction_build(&txn, None, None, None)));
112 }
113 }
114 return Ok(None);
116 }
117 };
118
119 let BlockInfo {
120 block,
121 statuses,
122 base_fee,
123 ..
124 } = self.block_info_by_eth_block_hash(eth_block_hash).await?;
125 match (block, statuses) {
126 (Some(block), Some(statuses)) => Ok(Some(transaction_build(
127 &block.transactions[index],
128 Some(&block),
129 Some(&statuses[index]),
130 Some(base_fee),
131 ))),
132 _ => Ok(None),
133 }
134 }
135
136 pub async fn transaction_by_block_hash_and_index(
137 &self,
138 hash: H256,
139 index: Index,
140 ) -> RpcResult<Option<Transaction>> {
141 let index = index.value();
142 let BlockInfo {
143 block,
144 statuses,
145 base_fee,
146 ..
147 } = self.block_info_by_eth_block_hash(hash).await?;
148
149 match (block, statuses) {
150 (Some(block), Some(statuses)) => {
151 if let (Some(transaction), Some(status)) =
152 (block.transactions.get(index), statuses.get(index))
153 {
154 Ok(Some(transaction_build(
155 transaction,
156 Some(&block),
157 Some(status),
158 Some(base_fee),
159 )))
160 } else {
161 Err(internal_err(format!("{index:?} is out of bounds")))
162 }
163 }
164 _ => Ok(None),
165 }
166 }
167
168 pub async fn transaction_by_block_number_and_index(
169 &self,
170 number: BlockNumberOrHash,
171 index: Index,
172 ) -> RpcResult<Option<Transaction>> {
173 let index = index.value();
174 let BlockInfo {
175 block,
176 statuses,
177 base_fee,
178 ..
179 } = self.block_info_by_number(number).await?;
180
181 match (block, statuses) {
182 (Some(block), Some(statuses)) => {
183 if let (Some(transaction), Some(status)) =
184 (block.transactions.get(index), statuses.get(index))
185 {
186 Ok(Some(transaction_build(
187 transaction,
188 Some(&block),
189 Some(status),
190 Some(base_fee),
191 )))
192 } else {
193 Err(internal_err(format!("{index:?} is out of bounds")))
194 }
195 }
196 _ => Ok(None),
197 }
198 }
199
200 pub async fn transaction_receipt(
201 &self,
202 block_info: &BlockInfo<B::Hash>,
203 hash: H256,
204 index: usize,
205 ) -> RpcResult<Option<Receipt>> {
206 let BlockInfo {
207 block,
208 receipts,
209 statuses,
210 substrate_hash,
211 ..
212 } = block_info.clone();
213 match (block, statuses, receipts) {
214 (Some(block), Some(statuses), Some(receipts)) => {
215 let block_hash = H256::from(keccak_256(&rlp::encode(&block.header)));
216 let receipt = receipts[index].clone();
217
218 let (logs, logs_bloom, status_code, cumulative_gas_used, gas_used) = if !block_info
219 .is_eip1559
220 {
221 match receipt {
223 ethereum::ReceiptV4::Legacy(ref d) => {
224 let index = core::cmp::min(receipts.len(), index + 1);
225 let cumulative_gas: u32 = receipts[..index]
226 .iter()
227 .map(|r| match r {
228 ethereum::ReceiptV4::Legacy(d) => Ok(d.used_gas.as_u32()),
229 _ => Err(internal_err(format!(
230 "Unknown receipt for request {hash}"
231 ))),
232 })
233 .sum::<RpcResult<u32>>()?;
234 (
235 d.logs.clone(),
236 d.logs_bloom,
237 d.status_code,
238 U256::from(cumulative_gas),
239 d.used_gas,
240 )
241 }
242 _ => {
243 return Err(internal_err(format!("Unknown receipt for request {hash}")))
244 }
245 }
246 } else {
247 match receipt {
248 ethereum::ReceiptV4::Legacy(ref d)
249 | ethereum::ReceiptV4::EIP2930(ref d)
250 | ethereum::ReceiptV4::EIP1559(ref d)
251 | ethereum::ReceiptV4::EIP7702(ref d) => {
252 let cumulative_gas = d.used_gas;
253 let gas_used = if index > 0 {
254 let previous_receipt = receipts[index - 1].clone();
255 let previous_gas_used = match previous_receipt {
256 ethereum::ReceiptV4::Legacy(d)
257 | ethereum::ReceiptV4::EIP2930(d)
258 | ethereum::ReceiptV4::EIP1559(d)
259 | ethereum::ReceiptV4::EIP7702(d) => d.used_gas,
260 };
261 cumulative_gas.saturating_sub(previous_gas_used)
262 } else {
263 cumulative_gas
264 };
265 (
266 d.logs.clone(),
267 d.logs_bloom,
268 d.status_code,
269 cumulative_gas,
270 gas_used,
271 )
272 }
273 }
274 };
275
276 let status = statuses[index].clone();
277 let mut cumulative_receipts = receipts;
278 cumulative_receipts.truncate((status.transaction_index + 1) as usize);
279 let transaction = block.transactions[index].clone();
280 let calculate_eip1559_effective_gas_price =
282 |max_priority_fee_per_gas: U256, max_fee_per_gas: U256| async move {
283 let parent_eth_hash = block.header.parent_hash;
284 let base_fee_block_substrate_hash = if parent_eth_hash.is_zero() {
285 substrate_hash
286 } else {
287 frontier_backend_client::load_hash::<B, C>(
288 self.client.as_ref(),
289 self.backend.as_ref(),
290 parent_eth_hash,
291 )
292 .await
293 .map_err(|err| internal_err(format!("{err:?}")))?
294 .ok_or(internal_err(
295 "Failed to retrieve substrate parent block hash",
296 ))?
297 };
298
299 let base_fee = self
300 .client
301 .runtime_api()
302 .gas_price(base_fee_block_substrate_hash)
303 .unwrap_or_default();
304
305 Ok::<ethereum_types::U256, jsonrpsee::types::error::ErrorObjectOwned>(
306 base_fee
307 .checked_add(max_priority_fee_per_gas)
308 .unwrap_or_else(U256::max_value)
309 .min(max_fee_per_gas),
310 )
311 };
312
313 let effective_gas_price = match &transaction {
314 EthereumTransaction::Legacy(t) => t.gas_price,
315 EthereumTransaction::EIP2930(t) => t.gas_price,
316 EthereumTransaction::EIP1559(t) => {
317 calculate_eip1559_effective_gas_price(
318 t.max_priority_fee_per_gas,
319 t.max_fee_per_gas,
320 )
321 .await?
322 }
323 EthereumTransaction::EIP7702(t) => {
324 calculate_eip1559_effective_gas_price(
325 t.max_priority_fee_per_gas,
326 t.max_fee_per_gas,
327 )
328 .await?
329 }
330 };
331
332 Ok(Some(Receipt {
333 transaction_hash: Some(status.transaction_hash),
334 transaction_index: Some(status.transaction_index.into()),
335 block_hash: Some(block_hash),
336 from: Some(status.from),
337 to: status.to,
338 block_number: Some(block.header.number),
339 cumulative_gas_used,
340 gas_used: Some(gas_used),
341 contract_address: status.contract_address,
342 logs: {
343 let mut pre_receipts_log_index = None;
344 if cumulative_receipts.len() > 0 {
345 cumulative_receipts.truncate(cumulative_receipts.len() - 1);
346 pre_receipts_log_index = Some(
347 cumulative_receipts
348 .iter()
349 .map(|r| match r {
350 ethereum::ReceiptV4::Legacy(d)
351 | ethereum::ReceiptV4::EIP2930(d)
352 | ethereum::ReceiptV4::EIP1559(d)
353 | ethereum::ReceiptV4::EIP7702(d) => d.logs.len() as u32,
354 })
355 .sum::<u32>(),
356 );
357 }
358 logs.iter()
359 .enumerate()
360 .map(|(i, log)| Log {
361 address: log.address,
362 topics: log.topics.clone(),
363 data: Bytes(log.data.clone()),
364 block_hash: Some(block_hash),
365 block_number: Some(block.header.number),
366 transaction_hash: Some(status.transaction_hash),
367 transaction_index: Some(status.transaction_index.into()),
368 log_index: Some(U256::from(
369 (pre_receipts_log_index.unwrap_or(0)) + i as u32,
370 )),
371 transaction_log_index: Some(U256::from(i)),
372 removed: false,
373 })
374 .collect()
375 },
376 status_code: Some(U64::from(status_code)),
377 logs_bloom,
378 state_root: None,
379 effective_gas_price,
380 transaction_type: match receipt {
381 ethereum::ReceiptV4::Legacy(_) => U256::from(0),
382 ethereum::ReceiptV4::EIP2930(_) => U256::from(1),
383 ethereum::ReceiptV4::EIP1559(_) => U256::from(2),
384 ethereum::ReceiptV4::EIP7702(_) => U256::from(4),
385 },
386 }))
387 }
388 _ => Ok(None),
389 }
390 }
391}