1pub(crate) mod runtime_api;
21pub(crate) mod storage_api;
22
23use crate::{
24 BlockId, BlockInfoProvider, BlockNumberOrTag, FeeHistoryProvider, FeeHistoryResult, Filter,
25 Log, ReceiptInfo, ReceiptProvider, SubxtBlockInfoProvider, SyncLabel, SyncingProgress,
26 SyncingStatus, TransactionTrace,
27 block_sync::SyncCheckpoint,
28 subxt_client::{self, SrcChainConfig, revive::calls::EthTransact},
29};
30use futures::TryStreamExt;
31use jsonrpsee::types::{ErrorObjectOwned, error::CALL_EXECUTION_FAILED_CODE};
32use pallet_revive::{
33 EthTransactError,
34 evm::{H256, TransactionSigned, U256, decode_revert_reason},
35};
36use pallet_revive_types::runtime_api::*;
37use runtime_api::RuntimeApi;
38use sp_runtime::traits::Block as BlockT;
39use sp_weights::Weight;
40use std::{
41 sync::{
42 Arc,
43 atomic::{AtomicBool, AtomicUsize, Ordering},
44 },
45 time::Duration,
46};
47use storage_api::StorageApi;
48use subxt::{
49 Config, OnlineClient,
50 backend::{StreamOf, StreamOfResults},
51 client::OnlineClientAtBlock,
52 config::{HashFor, RpcConfigFor},
53 rpcs::{
54 RpcClient,
55 client::reconnecting_rpc_client::{ExponentialBackoff, RpcClient as ReconnectingRpcClient},
56 methods::{
57 LegacyRpcMethods,
58 legacy::{SystemHealth, TransactionStatus},
59 },
60 rpc_params,
61 },
62};
63
64use thiserror::Error;
65use tokio::sync::{Mutex, mpsc};
66
67pub type SubstrateBlockHeader = <SrcChainConfig as Config>::Header;
69
70pub type SubstrateBlockNumber = u64;
72
73pub type SubstrateBlockHash = HashFor<SrcChainConfig>;
75
76pub type SubstrateBlock = OnlineClientAtBlock<SrcChainConfig>;
78
79pub type Balance = u128;
81
82#[derive(Debug, Clone, Copy, PartialEq)]
84pub enum SubscriptionType {
85 BestBlocks,
87 FinalizedBlocks,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
93pub enum SubmitError {
94 #[error("Transaction was usurped by another with the same nonce")]
96 Usurped,
97 #[error("Transaction was dropped")]
99 Dropped,
100 #[error("Transaction is invalid (e.g. bad nonce, signature, etc)")]
102 Invalid,
103 #[error("Transaction stream ended without status")]
105 StreamEnded,
106 #[error("Unknown transaction status")]
108 Unknown,
109}
110
111impl From<TransactionStatus<SubstrateBlockHash>> for SubmitError {
112 fn from(status: TransactionStatus<SubstrateBlockHash>) -> Self {
113 match status {
114 TransactionStatus::Usurped(_) => SubmitError::Usurped,
115 TransactionStatus::Dropped => SubmitError::Dropped,
116 TransactionStatus::Invalid => SubmitError::Invalid,
117 _ => SubmitError::Unknown,
118 }
119 }
120}
121
122#[derive(Error, Debug)]
124pub enum ClientError {
125 #[error(transparent)]
127 Jsonrpsee(#[from] jsonrpsee::core::ClientError),
128 #[error(transparent)]
130 SubxtError(#[from] subxt::Error),
131 #[error(transparent)]
132 RpcError(#[from] subxt::rpcs::Error),
133 #[error(transparent)]
135 SqlxError(#[from] sqlx::Error),
136 #[error(transparent)]
138 CodecError(#[from] codec::Error),
139 #[error("Invalid transaction: {0}")]
141 SubmitError(SubmitError),
142 #[error("contract reverted: {0:?}")]
144 TransactError(EthTransactError),
145 #[error("conversion failed")]
147 ConversionFailed,
148 #[error("hash not found")]
150 BlockNotFound,
151 #[error("Contract not found")]
153 ContractNotFound,
154 #[error("No Ethereum extrinsic found")]
155 EthExtrinsicNotFound,
156 #[error("transactionFeePaid event not found")]
158 TxFeeNotFound,
159 #[error("Failed to decode a raw payload into a signed transaction")]
161 TxDecodingFailed,
162 #[error("failed to recover eth address")]
164 RecoverEthAddressFailed,
165 #[error("Failed to filter logs")]
167 LogFilterFailed(#[from] anyhow::Error),
168 #[error("Receipt storage not found")]
170 ReceiptDataNotFound,
171 #[error("Ethereum block not found")]
173 EthereumBlockNotFound,
174 #[error("Receipt data length mismatch")]
176 ReceiptDataLengthMismatch,
177 #[error("Transaction submission timeout")]
179 Timeout,
180 #[error("None of the estimation methods were found")]
183 NoEstimationMethodSucceeded,
184 #[error("Genesis hash mismatch")]
186 ChainMismatch,
187 #[error("Sync boundary mismatch")]
189 SyncBoundaryMismatch,
190}
191
192impl ClientError {
193 pub(crate) fn is_chain_validation_error(&self) -> bool {
195 matches!(self, Self::ChainMismatch | Self::SyncBoundaryMismatch)
196 }
197}
198
199macro_rules! impl_from_subxt_subtype {
201 ($($ty:ty),* $(,)?) => {
202 $(
203 impl From<$ty> for ClientError {
204 fn from(err: $ty) -> Self {
205 ClientError::SubxtError(err.into())
206 }
207 }
208 )*
209 };
210}
211
212impl_from_subxt_subtype!(
213 subxt::error::OnlineClientAtBlockError,
214 subxt::error::OnlineClientError,
215 subxt::error::BackendError,
216 subxt::error::BlockError,
217 subxt::error::BlocksError,
218 subxt::error::RuntimeApiError,
219 subxt::error::EventsError,
220 subxt::error::ExtrinsicError,
221 subxt::error::ConstantError,
222 subxt::error::StorageError,
223 subxt::error::StorageValueError,
224);
225
226const LOG_TARGET: &str = "eth-rpc::client";
227const LOG_TARGET_SUBSCRIPTION: &str = "eth-rpc::subscription";
228
229const REVERT_CODE: i32 = 3;
230
231const NOTIFIER_CAPACITY: usize = 16;
232
233impl From<ClientError> for ErrorObjectOwned {
234 fn from(err: ClientError) -> Self {
235 match err {
236 ClientError::SubxtError(subxt::Error::BackendError(
237 subxt::error::BackendError::Rpc(subxt::error::RpcError::ClientError(
238 subxt::rpcs::Error::User(err),
239 )),
240 )) |
241 ClientError::RpcError(subxt::rpcs::Error::User(err)) => {
242 ErrorObjectOwned::owned::<Vec<u8>>(err.code, err.message, None)
243 },
244 ClientError::TransactError(EthTransactError::Data(data)) => {
245 let msg = match decode_revert_reason(&data) {
246 Some(reason) => format!("execution reverted: {reason}"),
247 None => "execution reverted".to_string(),
248 };
249
250 let data = format!("0x{}", hex::encode(data));
251 ErrorObjectOwned::owned::<String>(REVERT_CODE, msg, Some(data))
252 },
253 ClientError::TransactError(EthTransactError::Message(msg)) => {
254 ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None)
255 },
256 _ => {
257 ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, err.to_string(), None)
258 },
259 }
260 }
261}
262
263#[derive(Clone)]
265pub struct Client {
266 api: OnlineClient<SrcChainConfig>,
267 rpc_client: RpcClient,
268 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
269 receipt_provider: ReceiptProvider,
270 block_provider: SubxtBlockInfoProvider,
271 fee_history_provider: FeeHistoryProvider,
272 chain_id: u64,
273 max_block_weight: Weight,
274 automine: bool,
276 block_notifier: Option<tokio::sync::broadcast::Sender<H256>>,
278 subscription_lock: Arc<Mutex<()>>,
280
281 block_subscription_tx: tokio::sync::broadcast::Sender<BlockV1>,
283 log_subscription_tx: tokio::sync::broadcast::Sender<Log>,
285 is_archive: bool,
287 backfill_complete: Arc<AtomicBool>,
289 subscription_gap_queue: SubscriptionGapQueue,
291}
292
293pub(crate) struct GapFillRequest {
295 pub from_inclusive: SubstrateBlockNumber,
296 pub to_inclusive: SubstrateBlockNumber,
297}
298
299#[derive(Clone)]
301pub(crate) struct SubscriptionGapQueue {
302 tx: mpsc::Sender<GapFillRequest>,
304 pending: Arc<AtomicUsize>,
307}
308
309impl SubscriptionGapQueue {
310 pub(crate) fn new() -> (Self, mpsc::Receiver<GapFillRequest>) {
311 let (tx, rx) = mpsc::channel(32);
314 (Self { tx, pending: Arc::new(AtomicUsize::new(0)) }, rx)
315 }
316
317 pub fn detect_and_queue(&self, current: SubstrateBlockNumber, last: SubstrateBlockNumber) {
319 if current.saturating_sub(last) <= 1 {
320 return;
321 }
322
323 let from_inclusive = current.saturating_sub(1);
324 let to_inclusive = last.saturating_add(1);
325 let gap_len = from_inclusive.saturating_sub(to_inclusive) + 1;
326 self.pending.fetch_add(1, Ordering::Release);
327 match self.tx.try_send(GapFillRequest { from_inclusive, to_inclusive }) {
328 Ok(_) => {
329 log::info!(target: LOG_TARGET,
330 "๐ Subscription gap queue: queued #{from_inclusive} down to #{to_inclusive} ({gap_len} blocks)");
331 },
332 Err(err) => {
333 self.pending.fetch_sub(1, Ordering::Release);
334 log::warn!(target: LOG_TARGET,
335 "๐ Subscription gap queue error, dropping #{from_inclusive}..#{to_inclusive} ({gap_len} blocks): {err}");
336 },
337 }
338 }
339
340 pub fn mark_done(&self) {
342 let res = self
343 .pending
344 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| v.checked_sub(1));
345 if res.is_err() {
346 debug_assert!(false, "subscription gap queue pending counter underflowed");
347 log::error!(target: LOG_TARGET,
348 "๐ Subscription gap queue pending counter underflow, delete the database and restart with --eth-pruning=archive to resync");
349 }
350 }
351
352 pub fn has_pending(&self) -> bool {
354 self.pending.load(Ordering::Acquire) > 0
355 }
356}
357
358fn known_first_evm_block_for_chain(chain_id: u64) -> Option<u64> {
360 match chain_id {
361 420420417 => Some(4_367_914), 420420418 => Some(12_234_156), 420420419 => Some(11_405_259), 420420421 => Some(13_169_391), _ => None,
366 }
367}
368
369async fn chain_id(api: &OnlineClient<SrcChainConfig>) -> Result<u64, ClientError> {
371 let query = subxt_client::constants().revive().chain_id().unvalidated();
372 let at_block = api.at_current_block().await?;
373 at_block.constants().entry(query).map_err(|err| err.into())
374}
375
376async fn max_block_weight(api: &OnlineClient<SrcChainConfig>) -> Result<Weight, ClientError> {
378 let query = subxt_client::constants().system().block_weights().unvalidated();
379 let at_block = api.at_current_block().await?;
380 let weights = at_block.constants().entry(query)?;
381 let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block);
382 Ok(max_block.0)
383}
384
385async fn get_automine(rpc_client: &RpcClient) -> bool {
387 match rpc_client.request::<bool>("getAutomine", rpc_params![]).await {
388 Ok(val) => val,
389 Err(err) => {
390 log::info!(target: LOG_TARGET, "Node does not have getAutomine RPC. Defaulting to automine=false. error: {err:?}");
391 false
392 },
393 }
394}
395
396pub async fn connect(
399 node_rpc_url: &str,
400 max_request_size: u32,
401 max_response_size: u32,
402) -> Result<
403 (OnlineClient<SrcChainConfig>, RpcClient, LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>),
404 ClientError,
405> {
406 log::info!(target: LOG_TARGET, "๐ Connecting to node at: {node_rpc_url} ...");
407 let rpc_client = ReconnectingRpcClient::builder()
408 .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10)))
409 .max_request_size(max_request_size)
410 .max_response_size(max_response_size)
411 .build(node_rpc_url.to_string())
412 .await?;
413 let rpc_client = RpcClient::new(rpc_client);
414 log::info!(target: LOG_TARGET, "๐ Connected to node at: {node_rpc_url}");
415
416 let api = OnlineClient::<SrcChainConfig>::from_rpc_client(rpc_client.clone()).await?;
417 let rpc = LegacyRpcMethods::<RpcConfigFor<SrcChainConfig>>::new(rpc_client.clone());
418 Ok((api, rpc_client, rpc))
419}
420
421impl Client {
422 pub(crate) async fn new(
424 api: OnlineClient<SrcChainConfig>,
425 rpc_client: RpcClient,
426 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
427 block_provider: SubxtBlockInfoProvider,
428 receipt_provider: ReceiptProvider,
429 is_archive: bool,
430 subscription_gap_queue: SubscriptionGapQueue,
431 ) -> Result<Self, ClientError> {
432 let (chain_id, max_block_weight, automine) =
433 tokio::try_join!(chain_id(&api), max_block_weight(&api), async {
434 Ok(get_automine(&rpc_client).await)
435 },)?;
436
437 if !is_archive {
440 if let Some(known) = known_first_evm_block_for_chain(chain_id) {
441 let best = block_provider.latest_block_number().await;
442 if known > best {
443 log::debug!(
444 target: LOG_TARGET,
445 "Hardcoded first EVM block {known} exceeds best block {best} \
446 for chain {chain_id}, defaulting to 0"
447 );
448 receipt_provider.set_first_evm_block(0).await?;
449 }
450 }
451 }
452
453 let client = Self {
454 api,
455 rpc_client,
456 rpc,
457 receipt_provider,
458 block_provider,
459 fee_history_provider: FeeHistoryProvider::default(),
460 chain_id,
461 max_block_weight,
462 automine,
463 block_notifier: automine
464 .then(|| tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0),
465 subscription_lock: Arc::new(Mutex::new(())),
466 block_subscription_tx: tokio::sync::broadcast::channel(256).0,
467 log_subscription_tx: tokio::sync::broadcast::channel(1000).0,
468 is_archive,
469 backfill_complete: Arc::new(AtomicBool::new(false)),
470 subscription_gap_queue,
471 };
472
473 Ok(client)
474 }
475
476 pub(crate) fn mark_backfill_complete(&self) {
478 self.backfill_complete.store(true, Ordering::Release);
479 }
480
481 async fn advance_sync_head(&self, block_number: SubstrateBlockNumber, hash: H256) {
484 if !self.is_archive ||
485 !self.backfill_complete.load(Ordering::Acquire) ||
486 self.subscription_gap_queue.has_pending()
487 {
488 return;
489 }
490
491 if let Err(err) = self
492 .receipt_provider
493 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(block_number, hash))
494 .await
495 {
496 log::warn!(target: LOG_TARGET, "Failed to advance sync head: {err:?}");
497 }
498 }
499
500 pub fn create_block_notifier(&mut self) {
502 self.block_notifier = Some(tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0);
503 }
504
505 pub fn set_block_notifier(&mut self, notifier: Option<tokio::sync::broadcast::Sender<H256>>) {
507 self.block_notifier = notifier;
508 }
509
510 pub(crate) fn api(&self) -> &OnlineClient<SrcChainConfig> {
511 &self.api
512 }
513
514 pub(crate) fn receipt_provider(&self) -> &ReceiptProvider {
515 &self.receipt_provider
516 }
517
518 pub(crate) fn block_provider(&self) -> &SubxtBlockInfoProvider {
519 &self.block_provider
520 }
521
522 pub(crate) fn subscription_gap_queue(&self) -> &SubscriptionGapQueue {
523 &self.subscription_gap_queue
524 }
525
526 fn earliest_block_number(&self) -> SubstrateBlockNumber {
529 self.receipt_provider
530 .first_evm_block()
531 .or_else(|| known_first_evm_block_for_chain(self.chain_id))
532 .unwrap_or(0)
533 }
534
535 async fn subscribe_new_blocks<F, Fut>(
537 &self,
538 subscription_type: SubscriptionType,
539 callback: F,
540 ) -> Result<(), ClientError>
541 where
542 F: Fn(SubstrateBlock) -> Fut + Send + Sync,
543 Fut: std::future::Future<Output = Result<(), ClientError>> + Send,
544 {
545 let mut block_stream = match subscription_type {
546 SubscriptionType::BestBlocks => self.api.stream_best_blocks().await,
547 SubscriptionType::FinalizedBlocks => self.api.stream_blocks().await,
548 }
549 .inspect_err(|err| {
550 log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}");
551 })?;
552
553 let mut last_finalized_seen: Option<SubstrateBlockNumber> = None;
554
555 while let Some(block) = block_stream.next().await {
556 let block = match block {
557 Ok(block) => block,
558 Err(err) => {
559 let err: subxt::Error = err.into();
560 if err.is_disconnected_will_reconnect() {
561 log::warn!(
562 target: LOG_TARGET,
563 "The RPC connection was lost and we may have missed a few blocks \
564 ({subscription_type:?}, last finalized: {last_finalized_seen:?}): {err:?}"
565 );
566 continue;
567 }
568
569 log::error!(target: LOG_TARGET, "Failed to fetch block ({subscription_type:?}): {err:?}");
570 return Err(err.into());
571 },
572 };
573
574 let block = block.at().await.inspect_err(|err| {
575 log::error!(target: LOG_TARGET, "Failed to resolve streamed block: {err:?}");
576 })?;
577
578 let _guard = self.subscription_lock.lock().await;
580
581 let block_number = block.block_number();
582
583 if subscription_type == SubscriptionType::FinalizedBlocks {
585 if let Some(last) = last_finalized_seen {
586 self.subscription_gap_queue.detect_and_queue(block_number, last);
587 }
588 last_finalized_seen = Some(block_number);
590 }
591
592 log::trace!(target: LOG_TARGET_SUBSCRIPTION, "โณ Processing {subscription_type:?} block: {block_number}");
593 if let Err(err) = callback(block).await {
594 log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}");
595 } else {
596 log::trace!(target: LOG_TARGET_SUBSCRIPTION, "โ
Processed {subscription_type:?} block: {block_number}");
597 }
598 }
599
600 log::info!(target: LOG_TARGET, "Block subscription ended");
601 Ok(())
602 }
603
604 async fn process_block(
606 &self,
607 block: &SubstrateBlock,
608 ) -> Result<(BlockV1, Vec<ReceiptInfo>), ClientError> {
609 let block_number = block.block_number();
610
611 macro_rules! time {
612 ($label:expr, $expr:expr) => {{
613 let t = std::time::Instant::now();
614 let r = $expr;
615 log::trace!(
616 target: LOG_TARGET,
617 "โฑ๏ธ #{block_number} {}: {:?}",
618 $label, t.elapsed(),
619 );
620 r
621 }};
622 }
623
624 let eth_block = time!("eth_block", RuntimeApi::new(block.clone()).eth_block().await?);
625 let receipts = time!(
626 "receipts_from_block",
627 self.receipt_provider.receipts_from_block(block, eth_block.hash).await?
628 );
629 time!(
630 "insert_block_receipts",
631 self.receipt_provider
632 .insert_block_receipts(block, &receipts, ð_block.hash)
633 .await?
634 );
635
636 let (_, receipt_infos): (Vec<_>, Vec<_>) = receipts.into_iter().unzip();
637 self.fee_history_provider.update_fee_history(ð_block, &receipt_infos).await;
638
639 Ok((eth_block, receipt_infos))
640 }
641
642 pub async fn subscribe_and_cache_new_blocks(
644 &self,
645 subscription_type: SubscriptionType,
646 ) -> Result<(), ClientError> {
647 log::info!(target: LOG_TARGET, "๐ Subscribing to new blocks ({subscription_type:?})");
648 self.subscribe_new_blocks(subscription_type, |block| async {
649 let hash = block.block_hash();
650
651 match subscription_type {
652 SubscriptionType::BestBlocks => {
653 let (eth_block, _) = self.process_block(&block).await?;
654 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
655
656 if let Some(sender) = &self.block_notifier {
657 if sender.receiver_count() > 0 {
658 let _ = sender.send(hash);
659 }
660 }
661 if self.block_subscription_tx.receiver_count() > 0 {
662 let _ = self.block_subscription_tx.send(eth_block);
663 }
664 },
665 SubscriptionType::FinalizedBlocks => {
666 let block_number = block.block_number();
667 let (receipt_infos, eth_hash) = match self
668 .receipt_provider
669 .get_processed_eth_block_hash(block_number, hash)
670 .await
671 {
672 Some(eth_hash) => {
673 log::trace!(target: LOG_TARGET_SUBSCRIPTION,
674 "โฉ Finalized block #{block_number} already processed, \
675 skipping extraction");
676 (None, eth_hash)
677 },
678 None => {
679 let (eth_block, infos) = self.process_block(&block).await?;
680 (Some(infos), eth_block.hash)
681 },
682 };
683
684 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
685 self.advance_sync_head(block_number, hash).await;
686
687 if self.log_subscription_tx.receiver_count() > 0 {
688 let logs = match receipt_infos {
689 Some(infos) => infos.into_iter().flat_map(|r| r.logs).collect(),
690 None => {
691 self.receipt_provider
692 .logs_by_block_number(block_number, eth_hash)
693 .await?
694 },
695 };
696 for log in logs {
697 let _ = self.log_subscription_tx.send(log);
698 }
699 }
700 },
701 }
702
703 Ok(())
704 })
705 .await
706 }
707
708 pub async fn block_hash_for_tag(&self, at: BlockId) -> Result<SubstrateBlockHash, ClientError> {
710 match at {
711 BlockId::Hash(hash) => self
712 .resolve_substrate_hash(&H256::from(hash.block_hash.0))
713 .await
714 .ok_or(ClientError::EthereumBlockNotFound),
715 BlockId::Number(tag) => self
716 .block_by_number_or_tag(&tag)
717 .await?
718 .map(|block| block.block_hash())
719 .ok_or(ClientError::BlockNotFound),
720 }
721 }
722
723 pub async fn block_by_number_or_tag(
725 &self,
726 block: &BlockNumberOrTag,
727 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
728 match block {
729 BlockNumberOrTag::Number(n) => {
730 let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?;
731 self.block_by_number(n).await
732 },
733 BlockNumberOrTag::Finalized | BlockNumberOrTag::Safe => {
734 let block = self.block_provider.latest_finalized_block().await;
735 Ok(Some(block))
736 },
737 BlockNumberOrTag::Earliest => self.block_by_number(self.earliest_block_number()).await,
738 BlockNumberOrTag::Latest | BlockNumberOrTag::Pending => {
739 let block = self.block_provider.latest_block().await;
740 Ok(Some(block))
741 },
742 }
743 }
744
745 pub async fn block_for_tag(
750 &self,
751 at: BlockId,
752 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
753 match at {
754 BlockId::Hash(hash) => {
755 let Some(hash) = self.resolve_substrate_hash(&H256::from(hash.block_hash.0)).await
756 else {
757 return Ok(None);
758 };
759 self.block_by_hash(&hash).await
760 },
761 BlockId::Number(tag) => self.block_by_number_or_tag(&tag).await,
762 }
763 }
764
765 async fn resolve_tag_to_number(&self, tag: BlockNumberOrTag) -> Result<U256, ClientError> {
767 match tag {
768 BlockNumberOrTag::Number(n) => Ok(U256::from(n)),
769 BlockNumberOrTag::Earliest => Ok(U256::from(self.earliest_block_number())),
770 _ => Ok(self
771 .block_by_number_or_tag(&tag)
772 .await?
773 .ok_or(ClientError::BlockNotFound)?
774 .block_number()
775 .into()),
776 }
777 }
778
779 pub async fn storage_api(&self, block_hash: H256) -> Result<StorageApi, ClientError> {
781 Ok(StorageApi::new(self.api.at_block(block_hash).await?))
782 }
783
784 pub async fn runtime_api(&self, block_hash: H256) -> Result<RuntimeApi, ClientError> {
786 Ok(RuntimeApi::new(self.api.at_block(block_hash).await?))
787 }
788
789 pub async fn latest_finalized_block(&self) -> Arc<SubstrateBlock> {
791 self.block_provider.latest_finalized_block().await
792 }
793
794 pub async fn latest_block(&self) -> Arc<SubstrateBlock> {
796 self.block_provider.latest_block().await
797 }
798
799 async fn submit_transaction(
801 &self,
802 call: subxt::tx::StaticPayload<EthTransact>,
803 ) -> Result<StreamOfResults<TransactionStatus<SubstrateBlockHash>>, ClientError> {
804 let at_block = self.api.at_current_block().await?;
805 let ext = at_block.tx().create_unsigned(&call.unvalidated()).map_err(ClientError::from)?;
806
807 let sub = self
808 .rpc_client
809 .subscribe(
810 "author_submitAndWatchExtrinsic",
811 rpc_params![to_hex(ext.encoded())],
812 "author_unwatchExtrinsic",
813 )
814 .await?;
815
816 let sub = sub.map_err(|e| e.into());
817 Ok(StreamOf::new(Box::pin(sub)))
818 }
819
820 pub async fn submit(
822 &self,
823 call: subxt::tx::StaticPayload<EthTransact>,
824 ) -> Result<TransactionStatus<SubstrateBlockHash>, ClientError> {
825 let mut progress = self.submit_transaction(call).await.inspect_err(|err| {
826 log::debug!(target: LOG_TARGET, "Failed to submit transaction: {err:?}");
827 })?;
828
829 tokio::time::timeout(Duration::from_secs(5), async {
830 if let Some(status) = progress.next().await {
831 match status {
832 Ok(
833 tx @ (TransactionStatus::Future |
834 TransactionStatus::Ready |
835 TransactionStatus::Broadcast(_) |
838 TransactionStatus::InBlock(_) |
839 TransactionStatus::FinalityTimeout(_) |
840 TransactionStatus::Retracted(_) |
841 TransactionStatus::Finalized(_)),
842 ) => {
843 return Ok(tx);
844 },
845 Ok(
846 tx @ (TransactionStatus::Usurped(_) |
847 TransactionStatus::Dropped |
848 TransactionStatus::Invalid),
849 ) => {
850 return Err(ClientError::SubmitError(tx.into()));
851 },
852 Err(err) => {
853 log::debug!(target: LOG_TARGET, "Transaction submission failed: {err:?}");
854 return Err(ClientError::from(err));
855 },
856 }
857 }
858 return Err(ClientError::SubmitError(SubmitError::StreamEnded));
859 })
860 .await
861 .map_err(|_| ClientError::Timeout)?
862 }
863
864 pub async fn receipt(&self, tx_hash: &H256) -> Option<ReceiptInfo> {
866 self.receipt_provider.receipt_by_hash(tx_hash).await
867 }
868
869 pub async fn block_receipts(
873 &self,
874 at: BlockId,
875 ) -> Result<Option<Vec<ReceiptInfo>>, ClientError> {
876 let Some(block) = self.block_for_tag(at).await? else {
877 return Ok(None);
878 };
879 let receipts = self.receipt_provider.block_receipts(&block).await?;
880 Ok(Some(receipts.into_iter().map(|(_, receipt)| receipt).collect()))
881 }
882
883 pub async fn post_dispatch_weight(&self, tx_hash: &H256) -> Option<Weight> {
885 use crate::subxt_client::system::events::ExtrinsicSuccess;
886 let ReceiptInfo { block_hash, transaction_index, .. } = self.receipt(tx_hash).await?;
887 let block_hash = self.resolve_substrate_hash(&block_hash).await?;
888 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
889 let extrinsics = block.extrinsics().fetch().await.ok()?;
890 let ext = extrinsics.iter().nth(transaction_index.as_u32() as _)?.ok()?;
891 let event = ext.events().await.ok()?.find_first::<ExtrinsicSuccess>()?.ok()?;
892 Some(event.dispatch_info.weight.0)
893 }
894
895 pub async fn sync_state(
896 &self,
897 ) -> Result<sc_rpc::system::SyncState<SubstrateBlockNumber>, ClientError> {
898 let client = self.rpc_client.clone();
899 let sync_state: sc_rpc::system::SyncState<SubstrateBlockNumber> =
900 client.request("system_syncState", Default::default()).await?;
901 Ok(sync_state)
902 }
903
904 pub async fn syncing(&self) -> Result<SyncingStatus, ClientError> {
906 let health = self.rpc.system_health().await?;
907
908 let status = if health.is_syncing {
909 let sync_state = self.sync_state().await?;
910 SyncingStatus::SyncingProgress(SyncingProgress {
911 current_block: Some(sync_state.current_block.into()),
912 highest_block: Some(sync_state.highest_block.into()),
913 starting_block: Some(sync_state.starting_block.into()),
914 })
915 } else {
916 SyncingStatus::Bool(false)
917 };
918
919 Ok(status)
920 }
921
922 pub async fn receipt_by_hash_and_index(
924 &self,
925 block_hash: &H256,
926 transaction_index: usize,
927 ) -> Option<ReceiptInfo> {
928 self.receipt_provider
929 .receipt_by_block_hash_and_index(block_hash, transaction_index)
930 .await
931 }
932
933 pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option<TransactionSigned> {
934 self.receipt_provider.signed_tx_by_hash(tx_hash).await
935 }
936
937 pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option<usize> {
939 self.receipt_provider.receipts_count_per_block(block_hash).await
940 }
941
942 pub async fn receipt_by_ethereum_hash_and_index(
944 &self,
945 ethereum_hash: &H256,
946 transaction_index: usize,
947 ) -> Option<ReceiptInfo> {
948 let substrate_hash =
950 self.resolve_substrate_hash(ethereum_hash).await.unwrap_or_else(|| {
951 log::trace!(target: LOG_TARGET,
952 "receipt_by_ethereum_hash_and_index: no ETH-to-substrate mapping for \
953 {ethereum_hash:?}, falling back to substrate hash lookup");
954 *ethereum_hash
955 });
956 self.receipt_by_hash_and_index(&substrate_hash, transaction_index).await
957 }
958
959 pub async fn system_health(&self) -> Result<SystemHealth, ClientError> {
961 let health = self.rpc.system_health().await?;
962 Ok(health)
963 }
964
965 pub async fn block_number(&self) -> Result<SubstrateBlockNumber, ClientError> {
967 let latest_block = self.block_provider.latest_block().await;
968 Ok(latest_block.block_number())
969 }
970
971 pub async fn block_by_hash(
973 &self,
974 hash: &SubstrateBlockHash,
975 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
976 self.block_provider.block_by_hash(hash).await
977 }
978
979 pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option<H256> {
982 self.receipt_provider.get_substrate_hash(ethereum_hash).await
983 }
984
985 pub async fn resolve_ethereum_hash(&self, substrate_hash: &H256) -> Option<H256> {
988 self.receipt_provider.get_ethereum_hash(substrate_hash).await
989 }
990
991 pub async fn block_by_ethereum_hash(
994 &self,
995 ethereum_hash: &H256,
996 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
997 if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
999 return self.block_by_hash(&substrate_hash).await;
1000 }
1001
1002 log::trace!(target: LOG_TARGET,
1004 "block_by_ethereum_hash: no ETH-to-substrate mapping for {ethereum_hash:?}, \
1005 falling back to substrate hash lookup");
1006 self.block_by_hash(ethereum_hash).await
1007 }
1008
1009 pub async fn block_by_number(
1011 &self,
1012 block_number: SubstrateBlockNumber,
1013 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1014 self.block_provider.block_by_number(block_number).await
1015 }
1016
1017 pub async fn get_block_hash(
1019 &self,
1020 block_number: SubstrateBlockNumber,
1021 ) -> Result<Option<SubstrateBlockHash>, ClientError> {
1022 let maybe_block = self.block_provider.block_by_number(block_number).await?;
1023 Ok(maybe_block.map(|block| block.block_hash()))
1024 }
1025
1026 async fn tracing_block(
1027 &self,
1028 block_hash: H256,
1029 ) -> Result<
1030 sp_runtime::generic::Block<
1031 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
1032 sp_runtime::OpaqueExtrinsic,
1033 >,
1034 ClientError,
1035 > {
1036 let signed_block: Option<
1037 sp_runtime::generic::SignedBlock<
1038 sp_runtime::generic::Block<
1039 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
1040 sp_runtime::OpaqueExtrinsic,
1041 >,
1042 >,
1043 > = self.rpc_client.request("chain_getBlock", rpc_params![block_hash]).await?;
1044
1045 Ok(signed_block.ok_or(ClientError::BlockNotFound)?.block)
1046 }
1047
1048 pub async fn trace_block_by_number(
1050 &self,
1051 at: BlockNumberOrTag,
1052 config: TracerTypeV1,
1053 ) -> Result<Vec<TransactionTrace>, ClientError> {
1054 if self.receipt_provider.is_before_earliest_block(&at) {
1055 return Ok(vec![]);
1056 }
1057
1058 let block_hash = self.block_hash_for_tag(at.into()).await?;
1059 let block = self.tracing_block(block_hash).await?;
1060 let parent_hash = block.header().parent_hash;
1061 if parent_hash == Default::default() {
1063 return Ok(vec![]);
1064 }
1065 let runtime_api = self.runtime_api(parent_hash).await?;
1066 let traces = runtime_api.trace_block(block, config.clone()).await?;
1067
1068 let mut hashes = self
1069 .receipt_provider
1070 .block_transaction_hashes(&block_hash)
1071 .await
1072 .ok_or(ClientError::EthExtrinsicNotFound)?;
1073
1074 let traces = traces.into_iter().filter_map(|(index, trace)| {
1075 Some(TransactionTrace { tx_hash: hashes.remove(&(index as usize))?, trace })
1076 });
1077
1078 Ok(traces.collect())
1079 }
1080
1081 pub async fn trace_transaction(
1083 &self,
1084 transaction_hash: H256,
1085 config: TracerTypeV1,
1086 ) -> Result<TraceV1, ClientError> {
1087 let (block_hash, transaction_index) = self
1088 .receipt_provider
1089 .find_transaction(&transaction_hash)
1090 .await
1091 .ok_or(ClientError::EthExtrinsicNotFound)?;
1092
1093 let block = self.tracing_block(block_hash).await?;
1094 let parent_hash = block.header.parent_hash;
1095 let runtime_api = self.runtime_api(parent_hash).await?;
1096
1097 runtime_api.trace_tx(block, transaction_index as u32, config).await
1098 }
1099
1100 pub async fn trace_call(
1102 &self,
1103 transaction: GenericTransactionV1,
1104 block: BlockId,
1105 config: TracerTypeV1,
1106 state_overrides: Option<StateOverrideSetV1>,
1107 ) -> Result<TraceV1, ClientError> {
1108 let block_hash = self.block_hash_for_tag(block).await?;
1109 let runtime_api = self.runtime_api(block_hash).await?;
1110 runtime_api.trace_call(transaction, config, state_overrides).await
1111 }
1112
1113 pub async fn evm_block(
1115 &self,
1116 block: Arc<SubstrateBlock>,
1117 hydrated_transactions: bool,
1118 ) -> Option<BlockV1> {
1119 log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.block_hash());
1120
1121 if self
1122 .receipt_provider
1123 .is_before_earliest_block(&BlockNumberOrTag::Number(block.block_number().into()))
1124 {
1125 log::trace!(target: LOG_TARGET,
1126 "Block #{} is before receipt floor, skipping", block.block_number());
1127 return None;
1128 }
1129
1130 let runtime_api = RuntimeApi::new((*block).clone());
1136 match runtime_api.eth_block().await {
1137 Ok(mut eth_block) => {
1138 log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash);
1139
1140 if hydrated_transactions {
1141 let tx_infos = self
1143 .receipt_provider
1144 .receipts_from_block(&block, eth_block.hash)
1145 .await
1146 .inspect_err(|err| {
1147 log::trace!(target: LOG_TARGET,
1148 "Failed to extract receipts for block #{}: {err:?}",
1149 block.block_number());
1150 })
1151 .unwrap_or_default()
1152 .into_iter()
1153 .map(|(signed_tx, receipt)| receipt.transaction_info(signed_tx))
1154 .collect::<Vec<_>>();
1155
1156 eth_block.transactions = HashesOrTransactionInfosV1::TransactionInfos(tx_infos);
1157 }
1158
1159 Some(eth_block)
1160 },
1161 Err(err) => {
1162 log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.block_hash());
1163 None
1164 },
1165 }
1166 }
1167
1168 pub fn chain_id(&self) -> u64 {
1170 self.chain_id
1171 }
1172
1173 pub fn max_block_weight(&self) -> Weight {
1175 self.max_block_weight
1176 }
1177
1178 pub fn block_notifier(&self) -> Option<tokio::sync::broadcast::Sender<H256>> {
1180 self.block_notifier.clone()
1181 }
1182
1183 pub async fn logs(&self, filter: Option<Filter>) -> Result<Vec<Log>, ClientError> {
1185 let logs =
1186 self.receipt_provider
1187 .logs(filter, |tag| async move {
1188 self.resolve_tag_to_number(tag).await.map_err(Into::into)
1189 })
1190 .await
1191 .map_err(ClientError::LogFilterFailed)?;
1192
1193 Ok(logs)
1194 }
1195
1196 pub async fn fee_history(
1197 &self,
1198 block_count: u32,
1199 latest_block: BlockNumberOrTag,
1200 reward_percentiles: Option<Vec<f64>>,
1201 ) -> Result<FeeHistoryResult, ClientError> {
1202 let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else {
1203 return Err(ClientError::BlockNotFound);
1204 };
1205
1206 self.fee_history_provider
1207 .fee_history(block_count, latest_block.block_number(), reward_percentiles)
1208 .await
1209 }
1210
1211 pub fn is_automine(&self) -> bool {
1213 self.automine
1214 }
1215
1216 pub async fn get_automine(&self) -> bool {
1218 get_automine(&self.rpc_client).await
1219 }
1220
1221 pub fn get_block_subscription_rx(&self) -> tokio::sync::broadcast::Receiver<BlockV1> {
1223 self.block_subscription_tx.subscribe()
1224 }
1225
1226 pub fn get_log_subscription_rx(&self) -> tokio::sync::broadcast::Receiver<Log> {
1228 self.log_subscription_tx.subscribe()
1229 }
1230}
1231
1232fn to_hex(bytes: impl AsRef<[u8]>) -> String {
1233 format!("0x{}", hex::encode(bytes.as_ref()))
1234}