fc_rpc/eth/
transaction.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use std::sync::Arc;
20
21use ethereum::TransactionV3 as EthereumTransaction;
22use ethereum_types::{H256, U256, U64};
23use jsonrpsee::core::RpcResult;
24// Substrate
25use 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;
31// Frontier
32use 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				// If the transaction is not yet mapped in the frontier db,
76				// check for it in the transaction pool.
77				let mut xts: Vec<<B as BlockT>::Extrinsic> = Vec::new();
78				// Collect transactions in the ready validated pool.
79				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				// Collect transactions in the future validated pool.
87				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				// Unknown transaction.
115				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					// Pre-london frontier update stored receipts require cumulative gas calculation.
222					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				// Helper closure for EIP1559-style effective gas price calculation (used by EIP1559 and EIP7702)
281				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}