1pub(crate) mod runtime_api;
21pub(crate) mod storage_api;
22
23use crate::{
24 subxt_client::{self, revive::calls::types::EthTransact, SrcChainConfig},
25 BlockInfoProvider, BlockTag, FeeHistoryProvider, ReceiptProvider, SubxtBlockInfoProvider,
26 TracerType, TransactionInfo,
27};
28use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned};
29use pallet_revive::{
30 evm::{
31 decode_revert_reason, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, FeeHistoryResult,
32 Filter, GenericTransaction, HashesOrTransactionInfos, Log, ReceiptInfo, SyncingProgress,
33 SyncingStatus, Trace, TransactionSigned, TransactionTrace, H256,
34 },
35 EthTransactError,
36};
37use runtime_api::RuntimeApi;
38use sp_runtime::traits::Block as BlockT;
39use sp_weights::Weight;
40use std::{ops::Range, sync::Arc, time::Duration};
41use storage_api::StorageApi;
42use subxt::{
43 backend::{
44 legacy::{rpc_methods::SystemHealth, LegacyRpcMethods},
45 rpc::{
46 reconnecting_rpc_client::{ExponentialBackoff, RpcClient as ReconnectingRpcClient},
47 RpcClient,
48 },
49 },
50 config::{HashFor, Header},
51 ext::subxt_rpcs::rpc_params,
52 Config, OnlineClient,
53};
54use thiserror::Error;
55use tokio::sync::Mutex;
56
57pub type SubstrateBlock = subxt::blocks::Block<SrcChainConfig, OnlineClient<SrcChainConfig>>;
59
60pub type SubstrateBlockHeader = <SrcChainConfig as Config>::Header;
62
63pub type SubstrateBlockNumber = <SubstrateBlockHeader as Header>::Number;
65
66pub type SubstrateBlockHash = HashFor<SrcChainConfig>;
68
69pub type Balance = u128;
71
72#[derive(Debug, Clone, Copy, PartialEq)]
74pub enum SubscriptionType {
75 BestBlocks,
77 FinalizedBlocks,
79}
80
81#[derive(Error, Debug)]
83pub enum ClientError {
84 #[error(transparent)]
86 Jsonrpsee(#[from] jsonrpsee::core::ClientError),
87 #[error(transparent)]
89 SubxtError(#[from] subxt::Error),
90 #[error(transparent)]
91 RpcError(#[from] subxt::ext::subxt_rpcs::Error),
92 #[error(transparent)]
94 SqlxError(#[from] sqlx::Error),
95 #[error(transparent)]
97 CodecError(#[from] codec::Error),
98 #[error("contract reverted: {0:?}")]
100 TransactError(EthTransactError),
101 #[error("conversion failed")]
103 ConversionFailed,
104 #[error("hash not found")]
106 BlockNotFound,
107 #[error("Contract not found")]
109 ContractNotFound,
110 #[error("No Ethereum extrinsic found")]
111 EthExtrinsicNotFound,
112 #[error("transactionFeePaid event not found")]
114 TxFeeNotFound,
115 #[error("Failed to decode a raw payload into a signed transaction")]
117 TxDecodingFailed,
118 #[error("failed to recover eth address")]
120 RecoverEthAddressFailed,
121 #[error("Failed to filter logs")]
123 LogFilterFailed(#[from] anyhow::Error),
124 #[error("Receipt storage not found")]
126 ReceiptDataNotFound,
127 #[error("Ethereum block not found")]
129 EthereumBlockNotFound,
130 #[error("Receipt data length mismatch")]
132 ReceiptDataLengthMismatch,
133}
134const LOG_TARGET: &str = "eth-rpc::client";
135
136const REVERT_CODE: i32 = 3;
137
138const NOTIFIER_CAPACITY: usize = 16;
139impl From<ClientError> for ErrorObjectOwned {
140 fn from(err: ClientError) -> Self {
141 match err {
142 ClientError::SubxtError(subxt::Error::Rpc(subxt::error::RpcError::ClientError(
143 subxt::ext::subxt_rpcs::Error::User(err),
144 ))) |
145 ClientError::RpcError(subxt::ext::subxt_rpcs::Error::User(err)) =>
146 ErrorObjectOwned::owned::<Vec<u8>>(err.code, err.message, None),
147 ClientError::TransactError(EthTransactError::Data(data)) => {
148 let msg = match decode_revert_reason(&data) {
149 Some(reason) => format!("execution reverted: {reason}"),
150 None => "execution reverted".to_string(),
151 };
152
153 let data = format!("0x{}", hex::encode(data));
154 ErrorObjectOwned::owned::<String>(REVERT_CODE, msg, Some(data))
155 },
156 ClientError::TransactError(EthTransactError::Message(msg)) =>
157 ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None),
158 _ =>
159 ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, err.to_string(), None),
160 }
161 }
162}
163
164#[derive(Clone)]
166pub struct Client {
167 api: OnlineClient<SrcChainConfig>,
168 rpc_client: RpcClient,
169 rpc: LegacyRpcMethods<SrcChainConfig>,
170 receipt_provider: ReceiptProvider,
171 block_provider: SubxtBlockInfoProvider,
172 fee_history_provider: FeeHistoryProvider,
173 chain_id: u64,
174 max_block_weight: Weight,
175 automine: bool,
177 block_notifier: Option<tokio::sync::broadcast::Sender<H256>>,
179 subscription_lock: Arc<Mutex<()>>,
181}
182
183async fn chain_id(api: &OnlineClient<SrcChainConfig>) -> Result<u64, ClientError> {
185 let query = subxt_client::constants().revive().chain_id();
186 api.constants().at(&query).map_err(|err| err.into())
187}
188
189async fn max_block_weight(api: &OnlineClient<SrcChainConfig>) -> Result<Weight, ClientError> {
191 let query = subxt_client::constants().system().block_weights();
192 let weights = api.constants().at(&query)?;
193 let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block);
194 Ok(max_block.0)
195}
196
197async fn get_automine(rpc_client: &RpcClient) -> bool {
199 match rpc_client.request::<bool>("getAutomine", rpc_params![]).await {
200 Ok(val) => val,
201 Err(err) => {
202 log::info!(target: LOG_TARGET, "Node does not have getAutomine RPC. Defaulting to automine=false. error: {err:?}");
203 false
204 },
205 }
206}
207
208pub async fn connect(
211 node_rpc_url: &str,
212) -> Result<(OnlineClient<SrcChainConfig>, RpcClient, LegacyRpcMethods<SrcChainConfig>), ClientError>
213{
214 log::info!(target: LOG_TARGET, "๐ Connecting to node at: {node_rpc_url} ...");
215 let rpc_client = ReconnectingRpcClient::builder()
216 .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10)))
217 .build(node_rpc_url.to_string())
218 .await?;
219 let rpc_client = RpcClient::new(rpc_client);
220 log::info!(target: LOG_TARGET, "๐ Connected to node at: {node_rpc_url}");
221
222 let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?;
223 let rpc = LegacyRpcMethods::<SrcChainConfig>::new(rpc_client.clone());
224 Ok((api, rpc_client, rpc))
225}
226
227impl Client {
228 pub async fn new(
230 api: OnlineClient<SrcChainConfig>,
231 rpc_client: RpcClient,
232 rpc: LegacyRpcMethods<SrcChainConfig>,
233 block_provider: SubxtBlockInfoProvider,
234 receipt_provider: ReceiptProvider,
235 ) -> Result<Self, ClientError> {
236 let (chain_id, max_block_weight, automine) =
237 tokio::try_join!(chain_id(&api), max_block_weight(&api), async {
238 Ok(get_automine(&rpc_client).await)
239 },)?;
240
241 let client = Self {
242 api,
243 rpc_client,
244 rpc,
245 receipt_provider,
246 block_provider,
247 fee_history_provider: FeeHistoryProvider::default(),
248 chain_id,
249 max_block_weight,
250 automine,
251 block_notifier: automine
252 .then(|| tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0),
253 subscription_lock: Arc::new(Mutex::new(())),
254 };
255
256 Ok(client)
257 }
258
259 pub fn create_block_notifier(&mut self) {
261 self.block_notifier = Some(tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0);
262 }
263
264 pub fn set_block_notifier(&mut self, notifier: Option<tokio::sync::broadcast::Sender<H256>>) {
266 self.block_notifier = notifier;
267 }
268
269 async fn subscribe_past_blocks<F, Fut>(
271 &self,
272 range: Range<SubstrateBlockNumber>,
273 callback: F,
274 ) -> Result<(), ClientError>
275 where
276 F: Fn(Arc<SubstrateBlock>) -> Fut + Send + Sync,
277 Fut: std::future::Future<Output = Result<(), ClientError>> + Send,
278 {
279 let mut block = self
280 .block_provider
281 .block_by_number(range.end)
282 .await?
283 .ok_or(ClientError::BlockNotFound)?;
284
285 loop {
286 let block_number = block.number();
287 log::trace!(target: "eth-rpc::subscription", "Processing past block #{block_number}");
288
289 let parent_hash = block.header().parent_hash;
290 callback(block.clone()).await.inspect_err(|err| {
291 log::error!(target: "eth-rpc::subscription", "Failed to process past block #{block_number}: {err:?}");
292 })?;
293
294 if range.start < block_number {
295 block = self
296 .block_provider
297 .block_by_hash(&parent_hash)
298 .await?
299 .ok_or(ClientError::BlockNotFound)?;
300 } else {
301 return Ok(());
302 }
303 }
304 }
305
306 async fn subscribe_new_blocks<F, Fut>(
308 &self,
309 subscription_type: SubscriptionType,
310 callback: F,
311 ) -> Result<(), ClientError>
312 where
313 F: Fn(SubstrateBlock) -> Fut + Send + Sync,
314 Fut: std::future::Future<Output = Result<(), ClientError>> + Send,
315 {
316 let mut block_stream = match subscription_type {
317 SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await,
318 SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await,
319 }
320 .inspect_err(|err| {
321 log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}");
322 })?;
323
324 while let Some(block) = block_stream.next().await {
325 let block = match block {
326 Ok(block) => block,
327 Err(err) => {
328 if err.is_disconnected_will_reconnect() {
329 log::warn!(
330 target: LOG_TARGET,
331 "The RPC connection was lost and we may have missed a few blocks ({subscription_type:?}): {err:?}"
332 );
333 continue;
334 }
335
336 log::error!(target: LOG_TARGET, "Failed to fetch block ({subscription_type:?}): {err:?}");
337 return Err(err.into());
338 },
339 };
340
341 let _guard = self.subscription_lock.lock().await;
343
344 let block_number = block.number();
345 log::trace!(target: "eth-rpc::subscription", "โณ Processing {subscription_type:?} block: {block_number}");
346 if let Err(err) = callback(block).await {
347 log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}");
348 } else {
349 log::trace!(target: "eth-rpc::subscription", "โ
Processed {subscription_type:?} block: {block_number}");
350 }
351 }
352
353 log::info!(target: LOG_TARGET, "Block subscription ended");
354 Ok(())
355 }
356
357 pub async fn subscribe_and_cache_new_blocks(
359 &self,
360 subscription_type: SubscriptionType,
361 ) -> Result<(), ClientError> {
362 log::info!(target: LOG_TARGET, "๐ Subscribing to new blocks ({subscription_type:?})");
363 self.subscribe_new_blocks(subscription_type, |block| async {
364 let hash = block.hash();
365 let evm_block = self.runtime_api(hash).eth_block().await?;
366 let (_, receipts): (Vec<_>, Vec<_>) = self
367 .receipt_provider
368 .insert_block_receipts(&block, &evm_block.hash)
369 .await?
370 .into_iter()
371 .unzip();
372
373 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
374 self.fee_history_provider.update_fee_history(&evm_block, &receipts).await;
375
376 match (subscription_type, &self.block_notifier) {
378 (SubscriptionType::BestBlocks, Some(sender)) if sender.receiver_count() > 0 => {
379 let _ = sender.send(hash);
380 },
381 _ => {},
382 }
383 Ok(())
384 })
385 .await
386 }
387
388 pub async fn subscribe_and_cache_blocks(
390 &self,
391 index_last_n_blocks: SubstrateBlockNumber,
392 ) -> Result<(), ClientError> {
393 let last = self.latest_block().await.number().saturating_sub(1);
394 let range = last.saturating_sub(index_last_n_blocks)..last;
395 log::info!(target: LOG_TARGET, "๐๏ธ Indexing past blocks in range {range:?}");
396
397 self.subscribe_past_blocks(range, |block| async move {
398 let ethereum_hash = self
399 .runtime_api(block.hash())
400 .eth_block_hash(pallet_revive::evm::U256::from(block.number()))
401 .await?
402 .ok_or(ClientError::EthereumBlockNotFound)?;
403 self.receipt_provider.insert_block_receipts(&block, ðereum_hash).await?;
404 Ok(())
405 })
406 .await?;
407
408 log::info!(target: LOG_TARGET, "๐๏ธ Finished indexing past blocks");
409 Ok(())
410 }
411
412 pub async fn block_hash_for_tag(
414 &self,
415 at: BlockNumberOrTagOrHash,
416 ) -> Result<SubstrateBlockHash, ClientError> {
417 match at {
418 BlockNumberOrTagOrHash::BlockHash(hash) => self
419 .resolve_substrate_hash(&hash)
420 .await
421 .ok_or(ClientError::EthereumBlockNotFound),
422 BlockNumberOrTagOrHash::BlockNumber(block_number) => {
423 let n: SubstrateBlockNumber =
424 (block_number).try_into().map_err(|_| ClientError::ConversionFailed)?;
425 let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?;
426 Ok(hash)
427 },
428 BlockNumberOrTagOrHash::BlockTag(BlockTag::Finalized | BlockTag::Safe) => {
429 let block = self.latest_finalized_block().await;
430 Ok(block.hash())
431 },
432 BlockNumberOrTagOrHash::BlockTag(_) => {
433 let block = self.latest_block().await;
434 Ok(block.hash())
435 },
436 }
437 }
438
439 pub fn storage_api(&self, block_hash: H256) -> StorageApi {
441 StorageApi::new(self.api.storage().at(block_hash))
442 }
443
444 pub fn runtime_api(&self, block_hash: H256) -> RuntimeApi {
446 RuntimeApi::new(self.api.runtime_api().at(block_hash))
447 }
448
449 pub async fn latest_finalized_block(&self) -> Arc<SubstrateBlock> {
451 self.block_provider.latest_finalized_block().await
452 }
453
454 pub async fn latest_block(&self) -> Arc<SubstrateBlock> {
456 self.block_provider.latest_block().await
457 }
458
459 pub async fn submit(
461 &self,
462 call: subxt::tx::DefaultPayload<EthTransact>,
463 ) -> Result<(), ClientError> {
464 let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?;
465 let hash: H256 = self
466 .rpc_client
467 .request("author_submitExtrinsic", rpc_params![to_hex(ext.encoded())])
468 .await?;
469 log::debug!(target: LOG_TARGET, "Submitted transaction with substrate hash: {hash:?}");
470 Ok(())
471 }
472
473 pub async fn receipt(&self, tx_hash: &H256) -> Option<ReceiptInfo> {
475 self.receipt_provider.receipt_by_hash(tx_hash).await
476 }
477
478 pub async fn post_dispatch_weight(&self, tx_hash: &H256) -> Option<Weight> {
480 use crate::subxt_client::system::events::ExtrinsicSuccess;
481 let ReceiptInfo { block_hash, transaction_index, .. } = self.receipt(tx_hash).await?;
482 let block_hash = self.resolve_substrate_hash(&block_hash).await?;
483 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
484 let ext = block.extrinsics().await.ok()?.iter().nth(transaction_index.as_u32() as _)?;
485 let event = ext.events().await.ok()?.find_first::<ExtrinsicSuccess>().ok()??;
486 Some(event.dispatch_info.weight.0)
487 }
488
489 pub async fn sync_state(
490 &self,
491 ) -> Result<sc_rpc::system::SyncState<SubstrateBlockNumber>, ClientError> {
492 let client = self.rpc_client.clone();
493 let sync_state: sc_rpc::system::SyncState<SubstrateBlockNumber> =
494 client.request("system_syncState", Default::default()).await?;
495 Ok(sync_state)
496 }
497
498 pub async fn syncing(&self) -> Result<SyncingStatus, ClientError> {
500 let health = self.rpc.system_health().await?;
501
502 let status = if health.is_syncing {
503 let sync_state = self.sync_state().await?;
504 SyncingProgress {
505 current_block: Some(sync_state.current_block.into()),
506 highest_block: Some(sync_state.highest_block.into()),
507 starting_block: Some(sync_state.starting_block.into()),
508 }
509 .into()
510 } else {
511 SyncingStatus::Bool(false)
512 };
513
514 Ok(status)
515 }
516
517 pub async fn receipt_by_hash_and_index(
519 &self,
520 block_hash: &H256,
521 transaction_index: usize,
522 ) -> Option<ReceiptInfo> {
523 self.receipt_provider
524 .receipt_by_block_hash_and_index(block_hash, transaction_index)
525 .await
526 }
527
528 pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option<TransactionSigned> {
529 self.receipt_provider.signed_tx_by_hash(tx_hash).await
530 }
531
532 pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option<usize> {
534 self.receipt_provider.receipts_count_per_block(block_hash).await
535 }
536
537 pub async fn receipt_by_ethereum_hash_and_index(
539 &self,
540 ethereum_hash: &H256,
541 transaction_index: usize,
542 ) -> Option<ReceiptInfo> {
543 let substrate_hash =
545 self.resolve_substrate_hash(ethereum_hash).await.unwrap_or(*ethereum_hash);
546 self.receipt_by_hash_and_index(&substrate_hash, transaction_index).await
547 }
548
549 pub async fn system_health(&self) -> Result<SystemHealth, ClientError> {
551 let health = self.rpc.system_health().await?;
552 Ok(health)
553 }
554
555 pub async fn block_number(&self) -> Result<SubstrateBlockNumber, ClientError> {
557 let latest_block = self.block_provider.latest_block().await;
558 Ok(latest_block.number())
559 }
560
561 pub async fn get_block_hash(
563 &self,
564 block_number: SubstrateBlockNumber,
565 ) -> Result<Option<SubstrateBlockHash>, ClientError> {
566 let maybe_block = self.block_provider.block_by_number(block_number).await?;
567 Ok(maybe_block.map(|block| block.hash()))
568 }
569
570 pub async fn block_by_number_or_tag(
572 &self,
573 block: &BlockNumberOrTag,
574 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
575 match block {
576 BlockNumberOrTag::U256(n) => {
577 let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?;
578 self.block_by_number(n).await
579 },
580 BlockNumberOrTag::BlockTag(BlockTag::Finalized | BlockTag::Safe) => {
581 let block = self.block_provider.latest_finalized_block().await;
582 Ok(Some(block))
583 },
584 BlockNumberOrTag::BlockTag(_) => {
585 let block = self.block_provider.latest_block().await;
586 Ok(Some(block))
587 },
588 }
589 }
590
591 pub async fn block_by_hash(
593 &self,
594 hash: &SubstrateBlockHash,
595 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
596 self.block_provider.block_by_hash(hash).await
597 }
598
599 pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option<H256> {
602 self.receipt_provider.get_substrate_hash(ethereum_hash).await
603 }
604
605 pub async fn resolve_ethereum_hash(&self, substrate_hash: &H256) -> Option<H256> {
608 self.receipt_provider.get_ethereum_hash(substrate_hash).await
609 }
610
611 pub async fn block_by_ethereum_hash(
614 &self,
615 ethereum_hash: &H256,
616 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
617 if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
619 return self.block_by_hash(&substrate_hash).await;
620 }
621
622 self.block_by_hash(ethereum_hash).await
624 }
625
626 pub async fn block_by_number(
628 &self,
629 block_number: SubstrateBlockNumber,
630 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
631 self.block_provider.block_by_number(block_number).await
632 }
633
634 async fn tracing_block(
635 &self,
636 block_hash: H256,
637 ) -> Result<
638 sp_runtime::generic::Block<
639 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
640 sp_runtime::OpaqueExtrinsic,
641 >,
642 ClientError,
643 > {
644 let signed_block: sp_runtime::generic::SignedBlock<
645 sp_runtime::generic::Block<
646 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
647 sp_runtime::OpaqueExtrinsic,
648 >,
649 > = self
650 .rpc_client
651 .request("chain_getBlock", rpc_params![block_hash])
652 .await
653 .unwrap();
654
655 Ok(signed_block.block)
656 }
657
658 pub async fn trace_block_by_number(
660 &self,
661 at: BlockNumberOrTag,
662 config: TracerType,
663 ) -> Result<Vec<TransactionTrace>, ClientError> {
664 if self.receipt_provider.is_before_earliest_block(&at) {
665 return Ok(vec![]);
666 }
667
668 let block_hash = self.block_hash_for_tag(at.into()).await?;
669 let block = self.tracing_block(block_hash).await?;
670 let parent_hash = block.header().parent_hash;
671 let runtime_api = RuntimeApi::new(self.api.runtime_api().at(parent_hash));
672 let traces = runtime_api.trace_block(block, config.clone()).await?;
673
674 let mut hashes = self
675 .receipt_provider
676 .block_transaction_hashes(&block_hash)
677 .await
678 .ok_or(ClientError::EthExtrinsicNotFound)?;
679
680 let traces = traces.into_iter().filter_map(|(index, trace)| {
681 Some(TransactionTrace { tx_hash: hashes.remove(&(index as usize))?, trace })
682 });
683
684 Ok(traces.collect())
685 }
686
687 pub async fn trace_transaction(
689 &self,
690 transaction_hash: H256,
691 config: TracerType,
692 ) -> Result<Trace, ClientError> {
693 let (block_hash, transaction_index) = self
694 .receipt_provider
695 .find_transaction(&transaction_hash)
696 .await
697 .ok_or(ClientError::EthExtrinsicNotFound)?;
698
699 let block = self.tracing_block(block_hash).await?;
700 let parent_hash = block.header.parent_hash;
701 let runtime_api = self.runtime_api(parent_hash);
702
703 runtime_api.trace_tx(block, transaction_index as u32, config).await
704 }
705
706 pub async fn trace_call(
708 &self,
709 transaction: GenericTransaction,
710 block: BlockNumberOrTagOrHash,
711 config: TracerType,
712 ) -> Result<Trace, ClientError> {
713 let block_hash = self.block_hash_for_tag(block).await?;
714 let runtime_api = self.runtime_api(block_hash);
715 runtime_api.trace_call(transaction, config).await
716 }
717
718 pub async fn evm_block(
720 &self,
721 block: Arc<SubstrateBlock>,
722 hydrated_transactions: bool,
723 ) -> Option<Block> {
724 log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.hash());
725
726 match self.runtime_api(block.hash()).eth_block().await {
732 Ok(mut eth_block) => {
733 log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash);
734
735 if hydrated_transactions {
736 let tx_infos = self
738 .receipt_provider
739 .receipts_from_block(&block)
740 .await
741 .unwrap_or_default()
742 .into_iter()
743 .map(|(signed_tx, receipt)| TransactionInfo::new(&receipt, signed_tx))
744 .collect::<Vec<_>>();
745
746 eth_block.transactions = HashesOrTransactionInfos::TransactionInfos(tx_infos);
747 }
748
749 Some(eth_block)
750 },
751 Err(err) => {
752 log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.hash());
753 None
754 },
755 }
756 }
757
758 pub fn chain_id(&self) -> u64 {
760 self.chain_id
761 }
762
763 pub fn max_block_weight(&self) -> Weight {
765 self.max_block_weight
766 }
767
768 pub fn block_notifier(&self) -> Option<tokio::sync::broadcast::Sender<H256>> {
770 self.block_notifier.clone()
771 }
772
773 pub async fn logs(&self, filter: Option<Filter>) -> Result<Vec<Log>, ClientError> {
775 let logs =
776 self.receipt_provider.logs(filter).await.map_err(ClientError::LogFilterFailed)?;
777 Ok(logs)
778 }
779
780 pub async fn fee_history(
781 &self,
782 block_count: u32,
783 latest_block: BlockNumberOrTag,
784 reward_percentiles: Option<Vec<f64>>,
785 ) -> Result<FeeHistoryResult, ClientError> {
786 let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else {
787 return Err(ClientError::BlockNotFound);
788 };
789
790 self.fee_history_provider
791 .fee_history(block_count, latest_block.number(), reward_percentiles)
792 .await
793 }
794
795 pub fn is_automine(&self) -> bool {
797 self.automine
798 }
799
800 pub async fn get_automine(&self) -> bool {
802 get_automine(&self.rpc_client).await
803 }
804}
805
806fn to_hex(bytes: impl AsRef<[u8]>) -> String {
807 format!("0x{}", hex::encode(bytes.as_ref()))
808}