1use crate::{
18 client::{runtime_api::RuntimeApi, SubstrateBlock, SubstrateBlockNumber},
19 subxt_client::{
20 revive::{
21 calls::types::EthTransact,
22 events::{ContractEmitted, EthExtrinsicRevert},
23 },
24 SrcChainConfig,
25 },
26 ClientError, H160, LOG_TARGET,
27};
28
29use futures::{stream, StreamExt};
30use pallet_revive::{
31 create1,
32 evm::{GenericTransaction, Log, ReceiptGasInfo, ReceiptInfo, TransactionSigned, H256, U256},
33};
34use sp_core::keccak_256;
35use std::{future::Future, pin::Pin, sync::Arc};
36use subxt::{blocks::ExtrinsicDetails, OnlineClient};
37
38type FetchReceiptDataFn = Arc<
39 dyn Fn(H256) -> Pin<Box<dyn Future<Output = Option<Vec<ReceiptGasInfo>>> + Send>> + Send + Sync,
40>;
41
42type FetchEthBlockHashFn =
43 Arc<dyn Fn(H256, u64) -> Pin<Box<dyn Future<Output = Option<H256>> + Send>> + Send + Sync>;
44
45type RecoverEthAddressFn = Arc<dyn Fn(&TransactionSigned) -> Result<H160, ()> + Send + Sync>;
46
47#[derive(Clone)]
49pub struct ReceiptExtractor {
50 fetch_receipt_data: FetchReceiptDataFn,
52
53 fetch_eth_block_hash: FetchEthBlockHashFn,
55
56 earliest_receipt_block: Option<SubstrateBlockNumber>,
58
59 recover_eth_address: RecoverEthAddressFn,
61}
62
63impl ReceiptExtractor {
64 pub fn is_before_earliest_block(&self, block_number: SubstrateBlockNumber) -> bool {
66 block_number < self.earliest_receipt_block.unwrap_or_default()
67 }
68
69 pub async fn new(
71 api: OnlineClient<SrcChainConfig>,
72 earliest_receipt_block: Option<SubstrateBlockNumber>,
73 ) -> Result<Self, ClientError> {
74 Self::new_with_custom_address_recovery(
75 api,
76 earliest_receipt_block,
77 Arc::new(|signed_tx: &TransactionSigned| signed_tx.recover_eth_address()),
78 )
79 .await
80 }
81
82 pub async fn new_with_custom_address_recovery(
88 api: OnlineClient<SrcChainConfig>,
89 earliest_receipt_block: Option<SubstrateBlockNumber>,
90 recover_eth_address_fn: RecoverEthAddressFn,
91 ) -> Result<Self, ClientError> {
92 let api_inner = api.clone();
93 let fetch_eth_block_hash = Arc::new(move |block_hash, block_number| {
94 let api_inner = api_inner.clone();
95
96 let fut = async move {
97 let runtime_api = RuntimeApi::new(api_inner.runtime_api().at(block_hash));
98 runtime_api.eth_block_hash(U256::from(block_number)).await.ok().flatten()
99 };
100
101 Box::pin(fut) as Pin<Box<_>>
102 });
103
104 let api_inner = api.clone();
105 let fetch_receipt_data = Arc::new(move |block_hash| {
106 let api_inner = api_inner.clone();
107
108 let fut = async move {
109 let runtime_api = RuntimeApi::new(api_inner.runtime_api().at(block_hash));
110 runtime_api.eth_receipt_data().await.ok()
111 };
112
113 Box::pin(fut) as Pin<Box<_>>
114 });
115
116 Ok(Self {
117 fetch_receipt_data,
118 fetch_eth_block_hash,
119 earliest_receipt_block,
120 recover_eth_address: recover_eth_address_fn,
121 })
122 }
123
124 #[cfg(test)]
125 pub fn new_mock() -> Self {
126 let fetch_receipt_data = Arc::new(|_| Box::pin(std::future::ready(None)) as Pin<Box<_>>);
127 let fetch_eth_block_hash = Arc::new(|block_hash: H256, block_number: u64| {
129 let bytes: Vec<u8> = [block_hash.as_bytes(), &block_number.to_be_bytes()].concat();
131 let eth_block_hash = H256::from(keccak_256(&bytes));
132 Box::pin(std::future::ready(Some(eth_block_hash))) as Pin<Box<_>>
133 });
134
135 Self {
136 fetch_receipt_data,
137 fetch_eth_block_hash,
138 earliest_receipt_block: None,
139 recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| {
140 signed_tx.recover_eth_address()
141 }),
142 }
143 }
144
145 async fn extract_from_extrinsic(
147 &self,
148 substrate_block: &SubstrateBlock,
149 eth_block_hash: H256,
150 ext: subxt::blocks::ExtrinsicDetails<SrcChainConfig, subxt::OnlineClient<SrcChainConfig>>,
151 call: EthTransact,
152 receipt_gas_info: ReceiptGasInfo,
153 transaction_index: usize,
154 ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> {
155 let events = ext.events().await?;
156 let block_number: U256 = substrate_block.number().into();
157
158 let success = !events.has::<EthExtrinsicRevert>().inspect_err(|err| {
159 log::debug!(
160 target: LOG_TARGET,
161 "Failed to lookup for EthExtrinsicRevert event in block {block_number}: {err:?}"
162 );
163 })?;
164
165 let transaction_hash = H256(keccak_256(&call.payload));
166
167 let signed_tx =
168 TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?;
169 let from = (self.recover_eth_address)(&signed_tx).map_err(|_| {
170 log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx");
171 ClientError::RecoverEthAddressFailed
172 })?;
173
174 let tx_info = GenericTransaction::from_signed(
175 signed_tx.clone(),
176 receipt_gas_info.effective_gas_price,
177 Some(from),
178 );
179
180 let logs = events
182 .iter()
183 .filter_map(|event_details| {
184 let event_details = event_details.ok()?;
185 let event = event_details.as_event::<ContractEmitted>().ok()??;
186
187 Some(Log {
188 address: event.contract,
189 topics: event.topics,
190 data: Some(event.data.into()),
191 block_number,
192 transaction_hash,
193 transaction_index: transaction_index.into(),
194 block_hash: eth_block_hash,
195 log_index: event_details.index().into(),
196 ..Default::default()
197 })
198 })
199 .collect();
200
201 let contract_address = if tx_info.to.is_none() {
202 Some(create1(
203 &from,
204 tx_info
205 .nonce
206 .unwrap_or_default()
207 .try_into()
208 .map_err(|_| ClientError::ConversionFailed)?,
209 ))
210 } else {
211 None
212 };
213
214 let receipt = ReceiptInfo::new(
215 eth_block_hash,
216 block_number,
217 contract_address,
218 from,
219 logs,
220 tx_info.to,
221 receipt_gas_info.effective_gas_price,
222 U256::from(receipt_gas_info.gas_used),
223 success,
224 transaction_hash,
225 transaction_index.into(),
226 tx_info.r#type.unwrap_or_default(),
227 );
228 Ok((signed_tx, receipt))
229 }
230
231 pub async fn extract_from_block(
233 &self,
234 block: &SubstrateBlock,
235 ) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
236 if self.is_before_earliest_block(block.number()) {
237 return Ok(vec![]);
238 }
239
240 let ext_iter = self.get_block_extrinsics(block).await?;
241
242 let substrate_block_number = block.number() as u64;
243 let substrate_block_hash = block.hash();
244 let eth_block_hash =
245 (self.fetch_eth_block_hash)(substrate_block_hash, substrate_block_number)
246 .await
247 .unwrap_or(substrate_block_hash);
248
249 stream::iter(ext_iter)
251 .map(|(ext, call, receipt, ext_idx)| async move {
252 self.extract_from_extrinsic(block, eth_block_hash, ext, call, receipt, ext_idx)
253 .await
254 .inspect_err(|err| {
255 log::warn!(target: LOG_TARGET, "Error extracting extrinsic: {err:?}");
256 })
257 })
258 .buffered(10)
259 .collect::<Vec<Result<_, _>>>()
260 .await
261 .into_iter()
262 .collect::<Result<Vec<_>, _>>()
263 }
264
265 pub async fn get_block_extrinsics(
268 &self,
269 block: &SubstrateBlock,
270 ) -> Result<
271 impl Iterator<
272 Item = (
273 ExtrinsicDetails<SrcChainConfig, OnlineClient<SrcChainConfig>>,
274 EthTransact,
275 ReceiptGasInfo,
276 usize,
277 ),
278 >,
279 ClientError,
280 > {
281 let extrinsics = block.extrinsics().await.inspect_err(|err| {
283 log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number());
284 })?;
285
286 let receipt_data = (self.fetch_receipt_data)(block.hash())
287 .await
288 .ok_or(ClientError::ReceiptDataNotFound)?;
289 let extrinsics: Vec<_> = extrinsics
290 .iter()
291 .enumerate()
292 .flat_map(|(ext_idx, ext)| {
293 let call = ext.as_extrinsic::<EthTransact>().ok()??;
294 Some((ext, call, ext_idx))
295 })
296 .collect();
297
298 if receipt_data.len() != extrinsics.len() {
300 log::error!(
301 target: LOG_TARGET,
302 "Receipt data length ({}) does not match extrinsics length ({})",
303 receipt_data.len(),
304 extrinsics.len()
305 );
306 Err(ClientError::ReceiptDataLengthMismatch)
307 } else {
308 Ok(extrinsics
309 .into_iter()
310 .zip(receipt_data)
311 .map(|((extr, call, ext_idx), rec)| (extr, call, rec, ext_idx)))
312 }
313 }
314
315 pub async fn extract_from_transaction(
318 &self,
319 block: &SubstrateBlock,
320 transaction_index: usize,
321 ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> {
322 let ext_iter = self.get_block_extrinsics(block).await?;
323
324 let (ext, eth_call, receipt_gas_info, _) = ext_iter
325 .into_iter()
326 .find(|(_, _, _, ext_idx)| *ext_idx == transaction_index)
327 .ok_or(ClientError::EthExtrinsicNotFound)?;
328
329 let substrate_block_number = block.number() as u64;
330 let substrate_block_hash = block.hash();
331 let eth_block_hash =
332 (self.fetch_eth_block_hash)(substrate_block_hash, substrate_block_number)
333 .await
334 .unwrap_or(substrate_block_hash);
335
336 self.extract_from_extrinsic(
337 block,
338 eth_block_hash,
339 ext,
340 eth_call,
341 receipt_gas_info,
342 transaction_index,
343 )
344 .await
345 }
346
347 pub async fn get_ethereum_block_hash(
349 &self,
350 block_hash: &H256,
351 block_number: u64,
352 ) -> Option<H256> {
353 (self.fetch_eth_block_hash)(*block_hash, block_number).await
354 }
355}