1pub(crate) mod storage_api;
21pub(crate) mod version_aware_runtime_api;
22
23use crate::{
24 BlockId, BlockInfoProvider, BlockNumberOrTag, FeeHistoryProvider, FeeHistoryResult, Filter,
25 Log, ReceiptInfo, ReceiptProvider, SubxtBlockInfoProvider, SyncLabel, SyncingProgress,
26 SyncingStatus, TraceOutcome, 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 sp_runtime::traits::Block as BlockT;
38use sp_weights::Weight;
39use std::{
40 sync::{
41 Arc,
42 atomic::{AtomicBool, AtomicUsize, Ordering},
43 },
44 time::Duration,
45};
46use storage_api::StorageApi;
47use subxt::{
48 OnlineClient,
49 backend::{LegacyBackend, StreamOf, StreamOfResults},
50 client::OnlineClientAtBlock,
51 config::{HashFor, RpcConfigFor},
52 rpcs::{
53 RpcClient,
54 client::reconnecting_rpc_client::{ExponentialBackoff, RpcClient as ReconnectingRpcClient},
55 methods::{
56 LegacyRpcMethods,
57 legacy::{SystemHealth, TransactionStatus},
58 },
59 rpc_params,
60 },
61};
62
63use thiserror::Error;
64use tokio::sync::{Mutex, mpsc};
65use version_aware_runtime_api::{
66 CallRecordedOutput, TraceEntry, VersionAwareRuntimeApi, VersionAwareRuntimeApiProvider,
67};
68
69pub type SubstrateBlockNumber = u64;
71
72pub type SubstrateBlockHash = HashFor<SrcChainConfig>;
74
75pub type SubstrateBlock = OnlineClientAtBlock<SrcChainConfig>;
77
78pub type Balance = u128;
80
81#[derive(Debug, Clone, Copy, PartialEq)]
83pub enum SubscriptionType {
84 BestBlocks,
86 FinalizedBlocks,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
92pub enum SubmitError {
93 #[error("Transaction was usurped by another with the same nonce")]
95 Usurped,
96 #[error("Transaction was dropped")]
98 Dropped,
99 #[error("Transaction is invalid (e.g. bad nonce, signature, etc)")]
101 Invalid,
102 #[error("Transaction stream ended without status")]
104 StreamEnded,
105 #[error("Unknown transaction status")]
107 Unknown,
108}
109
110impl From<TransactionStatus<SubstrateBlockHash>> for SubmitError {
111 fn from(status: TransactionStatus<SubstrateBlockHash>) -> Self {
112 match status {
113 TransactionStatus::Usurped(_) => SubmitError::Usurped,
114 TransactionStatus::Dropped => SubmitError::Dropped,
115 TransactionStatus::Invalid => SubmitError::Invalid,
116 _ => SubmitError::Unknown,
117 }
118 }
119}
120
121#[derive(Error, Debug)]
123pub enum ClientError {
124 #[error(transparent)]
126 Jsonrpsee(#[from] jsonrpsee::core::ClientError),
127 #[error(transparent)]
129 SubxtError(#[from] subxt::Error),
130 #[error(transparent)]
131 RpcError(#[from] subxt::rpcs::Error),
132 #[error(transparent)]
134 SqlxError(#[from] sqlx::Error),
135 #[error(transparent)]
137 CodecError(#[from] codec::Error),
138 #[error("Invalid transaction: {0}")]
140 SubmitError(SubmitError),
141 #[error("contract reverted: {0:?}")]
143 TransactError(EthTransactError),
144 #[error("conversion failed")]
146 ConversionFailed,
147 #[error("hash not found")]
149 BlockNotFound,
150 #[error("Contract not found")]
152 ContractNotFound,
153 #[error("No Ethereum extrinsic found")]
154 EthExtrinsicNotFound,
155 #[error("trace unavailable: the node could not produce a trace for this transaction")]
157 TraceUnavailable,
158 #[error("transactionFeePaid event not found")]
160 TxFeeNotFound,
161 #[error("Failed to decode a raw payload into a signed transaction")]
163 TxDecodingFailed,
164 #[error("failed to recover eth address")]
166 RecoverEthAddressFailed,
167 #[error("Failed to filter logs")]
169 LogFilterFailed(#[from] anyhow::Error),
170 #[error("Receipt storage not found")]
172 ReceiptDataNotFound,
173 #[error("Ethereum block not found")]
175 EthereumBlockNotFound,
176 #[error("Receipt data length mismatch")]
178 ReceiptDataLengthMismatch,
179 #[error("Transaction submission timeout")]
181 Timeout,
182 #[error("Genesis hash mismatch")]
184 ChainMismatch,
185 #[error("Sync boundary mismatch")]
187 SyncBoundaryMismatch,
188 #[error("The `{0}` runtime API method is not supported at the queried block")]
190 UnsupportedRuntimeApiMethod(&'static str),
191}
192
193impl ClientError {
194 pub(crate) fn is_chain_validation_error(&self) -> bool {
196 matches!(self, Self::ChainMismatch | Self::SyncBoundaryMismatch)
197 }
198
199 pub(crate) fn recorded_unavailable_reason(&self) -> Option<RecordedUnavailable> {
202 use sc_rpc_api::state::error::{
203 CALL_RECORDED_DENIED_ERROR_CODE, CALL_RECORDED_UNSUPPORTED_ERROR_CODE,
204 };
205 const METHOD_NOT_FOUND: i32 = -32601;
206
207 let ClientError::RpcError(subxt::rpcs::Error::User(e)) = self else {
208 return None;
209 };
210 match e.code {
211 METHOD_NOT_FOUND => Some(RecordedUnavailable::MethodMissing),
212 CALL_RECORDED_DENIED_ERROR_CODE => Some(RecordedUnavailable::Denied),
213 CALL_RECORDED_UNSUPPORTED_ERROR_CODE => Some(RecordedUnavailable::NoRecorder),
214 _ => None,
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub(crate) enum RecordedUnavailable {
222 MethodMissing,
224 Denied,
226 NoRecorder,
228}
229
230impl RecordedUnavailable {
231 pub(crate) fn is_degraded(self) -> bool {
233 matches!(self, Self::MethodMissing | Self::Denied)
234 }
235}
236
237macro_rules! impl_from_subxt_subtype {
239 ($($ty:ty),* $(,)?) => {
240 $(
241 impl From<$ty> for ClientError {
242 fn from(err: $ty) -> Self {
243 ClientError::SubxtError(err.into())
244 }
245 }
246 )*
247 };
248}
249
250impl_from_subxt_subtype!(
251 subxt::error::OnlineClientAtBlockError,
252 subxt::error::OnlineClientError,
253 subxt::error::BackendError,
254 subxt::error::BlockError,
255 subxt::error::BlocksError,
256 subxt::error::RuntimeApiError,
257 subxt::error::EventsError,
258 subxt::error::ExtrinsicError,
259 subxt::error::ConstantError,
260 subxt::error::StorageError,
261 subxt::error::StorageValueError,
262);
263
264const LOG_TARGET: &str = "eth-rpc::client";
265const LOG_TARGET_SUBSCRIPTION: &str = "eth-rpc::subscription";
266
267const REVERT_CODE: i32 = 3;
268
269const NOTIFIER_CAPACITY: usize = 16;
270
271impl From<ClientError> for ErrorObjectOwned {
272 fn from(err: ClientError) -> Self {
273 match err {
274 ClientError::SubxtError(subxt::Error::BackendError(
275 subxt::error::BackendError::Rpc(subxt::error::RpcError::ClientError(
276 subxt::rpcs::Error::User(err),
277 )),
278 )) |
279 ClientError::RpcError(subxt::rpcs::Error::User(err)) => {
280 ErrorObjectOwned::owned::<Vec<u8>>(err.code, err.message, None)
281 },
282 ClientError::TransactError(EthTransactError::Data(data)) => {
283 let msg = match decode_revert_reason(&data) {
284 Some(reason) => format!("execution reverted: {reason}"),
285 None => "execution reverted".to_string(),
286 };
287
288 let data = format!("0x{}", hex::encode(data));
289 ErrorObjectOwned::owned::<String>(REVERT_CODE, msg, Some(data))
290 },
291 ClientError::TransactError(EthTransactError::Message(msg)) => {
292 ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, msg, None)
293 },
294 _ => {
295 ErrorObjectOwned::owned::<String>(CALL_EXECUTION_FAILED_CODE, err.to_string(), None)
296 },
297 }
298 }
299}
300
301#[derive(Clone)]
303pub struct Client {
304 api: OnlineClient<SrcChainConfig>,
305 rpc_client: RpcClient,
306 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
307 receipt_provider: ReceiptProvider,
308 block_provider: SubxtBlockInfoProvider,
309 fee_history_provider: FeeHistoryProvider,
310 chain_id: u64,
311 max_block_weight: Weight,
312 automine: bool,
314 block_notifier: Option<tokio::sync::broadcast::Sender<H256>>,
316 subscription_lock: Arc<Mutex<()>>,
318
319 block_subscription_tx: tokio::sync::broadcast::Sender<BlockV1>,
321 log_subscription_tx: tokio::sync::broadcast::Sender<Log>,
323 is_archive: bool,
325 backfill_complete: Arc<AtomicBool>,
327 subscription_gap_queue: SubscriptionGapQueue,
329 runtime_api_provider: VersionAwareRuntimeApiProvider,
331}
332
333pub(crate) struct GapFillRequest {
335 pub from_inclusive: SubstrateBlockNumber,
336 pub to_inclusive: SubstrateBlockNumber,
337}
338
339#[derive(Clone)]
341pub(crate) struct SubscriptionGapQueue {
342 tx: mpsc::Sender<GapFillRequest>,
344 pending: Arc<AtomicUsize>,
347}
348
349impl SubscriptionGapQueue {
350 pub(crate) fn new() -> (Self, mpsc::Receiver<GapFillRequest>) {
351 let (tx, rx) = mpsc::channel(32);
354 (Self { tx, pending: Arc::new(AtomicUsize::new(0)) }, rx)
355 }
356
357 pub fn detect_and_queue(&self, current: SubstrateBlockNumber, last: SubstrateBlockNumber) {
359 if current.saturating_sub(last) <= 1 {
360 return;
361 }
362
363 let from_inclusive = current.saturating_sub(1);
364 let to_inclusive = last.saturating_add(1);
365 let gap_len = from_inclusive.saturating_sub(to_inclusive) + 1;
366 self.pending.fetch_add(1, Ordering::Release);
367 match self.tx.try_send(GapFillRequest { from_inclusive, to_inclusive }) {
368 Ok(_) => {
369 log::info!(target: LOG_TARGET,
370 "๐ Subscription gap queue: queued #{from_inclusive} down to #{to_inclusive} ({gap_len} blocks)");
371 },
372 Err(err) => {
373 self.pending.fetch_sub(1, Ordering::Release);
374 log::warn!(target: LOG_TARGET,
375 "๐ Subscription gap queue error, dropping #{from_inclusive}..#{to_inclusive} ({gap_len} blocks): {err}");
376 },
377 }
378 }
379
380 pub fn mark_done(&self) {
382 let res = self
383 .pending
384 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |v| v.checked_sub(1));
385 if res.is_err() {
386 debug_assert!(false, "subscription gap queue pending counter underflowed");
387 log::error!(target: LOG_TARGET,
388 "๐ Subscription gap queue pending counter underflow, delete the database and restart with --eth-pruning=archive to resync");
389 }
390 }
391
392 pub fn has_pending(&self) -> bool {
394 self.pending.load(Ordering::Acquire) > 0
395 }
396}
397
398fn known_first_evm_block_for_chain(chain_id: u64) -> Option<u64> {
400 match chain_id {
401 420420417 => Some(4_367_914), 420420418 => Some(12_234_156), 420420419 => Some(11_405_259), 420420421 => Some(13_169_391), _ => None,
406 }
407}
408
409async fn max_block_weight(api: &OnlineClient<SrcChainConfig>) -> Result<Weight, ClientError> {
411 let query = subxt_client::constants().system().block_weights().unvalidated();
412 let at_block = api.at_current_block().await?;
413 let weights = at_block.constants().entry(query)?;
414 let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block);
415 Ok(max_block.0)
416}
417
418async fn get_automine(rpc_client: &RpcClient) -> bool {
420 match rpc_client.request::<bool>("getAutomine", rpc_params![]).await {
421 Ok(val) => val,
422 Err(err) => {
423 log::info!(target: LOG_TARGET, "Node does not have getAutomine RPC. Defaulting to automine=false. error: {err:?}");
424 false
425 },
426 }
427}
428
429pub async fn connect(
432 node_rpc_url: &str,
433 max_request_size: u32,
434 max_response_size: u32,
435) -> Result<
436 (OnlineClient<SrcChainConfig>, RpcClient, LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>),
437 ClientError,
438> {
439 log::info!(target: LOG_TARGET, "๐ Connecting to node at: {node_rpc_url} ...");
440 let rpc_client = ReconnectingRpcClient::builder()
441 .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10)))
442 .max_request_size(max_request_size)
443 .max_response_size(max_response_size)
444 .build(node_rpc_url.to_string())
445 .await?;
446 let rpc_client = RpcClient::new(rpc_client);
447 log::info!(target: LOG_TARGET, "๐ Connected to node at: {node_rpc_url}");
448
449 let backend = Arc::new(LegacyBackend::builder().build(rpc_client.clone()));
454 let api = OnlineClient::<SrcChainConfig>::from_backend(backend).await?;
455 let rpc = LegacyRpcMethods::<RpcConfigFor<SrcChainConfig>>::new(rpc_client.clone());
456 Ok((api, rpc_client, rpc))
457}
458
459impl Client {
460 pub(crate) async fn new(
462 api: OnlineClient<SrcChainConfig>,
463 rpc_client: RpcClient,
464 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
465 block_provider: SubxtBlockInfoProvider,
466 receipt_provider: ReceiptProvider,
467 is_archive: bool,
468 subscription_gap_queue: SubscriptionGapQueue,
469 runtime_api_provider: VersionAwareRuntimeApiProvider,
470 chain_id: u64,
471 ) -> Result<Self, ClientError> {
472 let (max_block_weight, automine) = tokio::try_join!(max_block_weight(&api), async {
473 Ok(get_automine(&rpc_client).await)
474 },)?;
475
476 let latest_finalized_block = block_provider.latest_finalized_block().await;
479 runtime_api_provider.at(latest_finalized_block.block_hash()).await?;
480
481 if !is_archive {
484 if let Some(known) = known_first_evm_block_for_chain(chain_id) {
485 let best = block_provider.latest_block_number().await;
486 if known > best {
487 log::debug!(
488 target: LOG_TARGET,
489 "Hardcoded first EVM block {known} exceeds best block {best} \
490 for chain {chain_id}, defaulting to 0"
491 );
492 receipt_provider.set_first_evm_block(0).await?;
493 }
494 }
495 }
496
497 let client = Self {
498 api,
499 rpc_client,
500 rpc,
501 receipt_provider,
502 block_provider,
503 fee_history_provider: FeeHistoryProvider::default(),
504 chain_id,
505 max_block_weight,
506 automine,
507 block_notifier: automine
508 .then(|| tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0),
509 subscription_lock: Arc::new(Mutex::new(())),
510 block_subscription_tx: tokio::sync::broadcast::channel(256).0,
511 log_subscription_tx: tokio::sync::broadcast::channel(1000).0,
512 is_archive,
513 backfill_complete: Arc::new(AtomicBool::new(false)),
514 subscription_gap_queue,
515 runtime_api_provider,
516 };
517
518 Ok(client)
519 }
520
521 pub(crate) fn mark_backfill_complete(&self) {
523 self.backfill_complete.store(true, Ordering::Release);
524 }
525
526 async fn advance_sync_head(&self, block_number: SubstrateBlockNumber, hash: H256) {
529 if !self.is_archive ||
530 !self.backfill_complete.load(Ordering::Acquire) ||
531 self.subscription_gap_queue.has_pending()
532 {
533 return;
534 }
535
536 if let Err(err) = self
537 .receipt_provider
538 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(block_number, hash))
539 .await
540 {
541 log::warn!(target: LOG_TARGET, "Failed to advance sync head: {err:?}");
542 }
543 }
544
545 pub fn create_block_notifier(&mut self) {
547 self.block_notifier = Some(tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0);
548 }
549
550 pub fn set_block_notifier(&mut self, notifier: Option<tokio::sync::broadcast::Sender<H256>>) {
552 self.block_notifier = notifier;
553 }
554
555 pub(crate) fn api(&self) -> &OnlineClient<SrcChainConfig> {
556 &self.api
557 }
558
559 pub(crate) fn receipt_provider(&self) -> &ReceiptProvider {
560 &self.receipt_provider
561 }
562
563 pub(crate) fn block_provider(&self) -> &SubxtBlockInfoProvider {
564 &self.block_provider
565 }
566
567 pub(crate) fn subscription_gap_queue(&self) -> &SubscriptionGapQueue {
568 &self.subscription_gap_queue
569 }
570
571 fn earliest_block_number(&self) -> SubstrateBlockNumber {
574 self.receipt_provider
575 .first_evm_block()
576 .or_else(|| known_first_evm_block_for_chain(self.chain_id))
577 .unwrap_or(0)
578 }
579
580 async fn subscribe_new_blocks<F, Fut>(
582 &self,
583 subscription_type: SubscriptionType,
584 callback: F,
585 ) -> Result<(), ClientError>
586 where
587 F: Fn(SubstrateBlock) -> Fut + Send + Sync,
588 Fut: std::future::Future<Output = Result<(), ClientError>> + Send,
589 {
590 let mut block_stream = match subscription_type {
591 SubscriptionType::BestBlocks => self.api.stream_best_blocks().await,
592 SubscriptionType::FinalizedBlocks => self.api.stream_blocks().await,
593 }
594 .inspect_err(|err| {
595 log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}");
596 })?;
597
598 let mut last_finalized_seen: Option<SubstrateBlockNumber> = None;
599
600 while let Some(block) = block_stream.next().await {
601 let block = match block {
602 Ok(block) => block,
603 Err(err) => {
604 let err: subxt::Error = err.into();
605 if err.is_disconnected_will_reconnect() {
606 log::warn!(
607 target: LOG_TARGET,
608 "The RPC connection was lost and we may have missed a few blocks \
609 ({subscription_type:?}, last finalized: {last_finalized_seen:?}): {err:?}"
610 );
611 continue;
612 }
613
614 log::error!(target: LOG_TARGET, "Failed to fetch block ({subscription_type:?}): {err:?}");
615 return Err(err.into());
616 },
617 };
618
619 let block = match block.at().await {
624 Ok(block) => block,
625 Err(err) => {
626 log::warn!(
627 target: LOG_TARGET,
628 "Failed to resolve streamed {subscription_type:?} block #{} ({:?}), skipping: {err:?}",
629 block.number(),
630 block.hash(),
631 );
632 continue;
633 },
634 };
635
636 let _guard = self.subscription_lock.lock().await;
638
639 let block_number = block.block_number();
640
641 if subscription_type == SubscriptionType::FinalizedBlocks {
643 if let Some(last) = last_finalized_seen {
644 self.subscription_gap_queue.detect_and_queue(block_number, last);
645 }
646 last_finalized_seen = Some(block_number);
648 }
649
650 log::trace!(target: LOG_TARGET_SUBSCRIPTION, "โณ Processing {subscription_type:?} block: {block_number}");
651 if let Err(err) = callback(block).await {
652 log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}");
653 } else {
654 log::trace!(target: LOG_TARGET_SUBSCRIPTION, "โ
Processed {subscription_type:?} block: {block_number}");
655 }
656 }
657
658 log::info!(target: LOG_TARGET, "Block subscription ended");
659 Ok(())
660 }
661
662 async fn process_block(
664 &self,
665 block: &SubstrateBlock,
666 ) -> Result<(BlockV1, Vec<ReceiptInfo>), ClientError> {
667 let block_number = block.block_number();
668 let hash = block.block_hash();
669
670 macro_rules! time {
671 ($label:expr, $expr:expr) => {{
672 let t = std::time::Instant::now();
673 let r = $expr;
674 log::trace!(
675 target: LOG_TARGET,
676 "โฑ๏ธ #{block_number} {}: {:?}",
677 $label, t.elapsed(),
678 );
679 r
680 }};
681 }
682
683 let eth_block = time!(
684 "eth_block",
685 self.runtime_api(hash)
686 .await?
687 .eth_block()
688 .ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_block"))?
689 .await?
690 );
691 let receipts = time!(
692 "receipts_from_block",
693 self.receipt_provider.receipts_from_block(block, eth_block.hash).await?
694 );
695 time!(
696 "insert_block_receipts",
697 self.receipt_provider
698 .insert_block_receipts(block, &receipts, ð_block.hash)
699 .await?
700 );
701
702 let (_, receipt_infos): (Vec<_>, Vec<_>) = receipts.into_iter().unzip();
703 self.fee_history_provider.update_fee_history(ð_block, &receipt_infos).await;
704
705 Ok((eth_block, receipt_infos))
706 }
707
708 pub async fn subscribe_and_cache_new_blocks(
710 &self,
711 subscription_type: SubscriptionType,
712 ) -> Result<(), ClientError> {
713 log::info!(target: LOG_TARGET, "๐ Subscribing to new blocks ({subscription_type:?})");
714 self.subscribe_new_blocks(subscription_type, |block| async {
715 let hash = block.block_hash();
716
717 match subscription_type {
718 SubscriptionType::BestBlocks => {
719 let (eth_block, _) = self.process_block(&block).await?;
720 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
721
722 if let Some(sender) = &self.block_notifier {
723 if sender.receiver_count() > 0 {
724 let _ = sender.send(hash);
725 }
726 }
727 if self.block_subscription_tx.receiver_count() > 0 {
728 let _ = self.block_subscription_tx.send(eth_block);
729 }
730 },
731 SubscriptionType::FinalizedBlocks => {
732 let block_number = block.block_number();
733 let (receipt_infos, eth_hash) = match self
734 .receipt_provider
735 .get_processed_eth_block_hash(block_number, hash)
736 .await
737 {
738 Some(eth_hash) => {
739 log::trace!(target: LOG_TARGET_SUBSCRIPTION,
740 "โฉ Finalized block #{block_number} already processed, \
741 skipping extraction");
742 (None, eth_hash)
743 },
744 None => {
745 let (eth_block, infos) = self.process_block(&block).await?;
746 (Some(infos), eth_block.hash)
747 },
748 };
749
750 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
751 self.advance_sync_head(block_number, hash).await;
752
753 if self.log_subscription_tx.receiver_count() > 0 {
754 let logs = match receipt_infos {
755 Some(infos) => infos.into_iter().flat_map(|r| r.logs).collect(),
756 None => {
757 self.receipt_provider
758 .logs_by_block_number(block_number, eth_hash)
759 .await?
760 },
761 };
762 for log in logs {
763 let _ = self.log_subscription_tx.send(log);
764 }
765 }
766 },
767 }
768
769 Ok(())
770 })
771 .await
772 }
773
774 pub async fn block_hash_for_tag(&self, at: BlockId) -> Result<SubstrateBlockHash, ClientError> {
776 match at {
777 BlockId::Hash(hash) => self
778 .resolve_substrate_hash(&H256::from(hash.block_hash.0))
779 .await
780 .ok_or(ClientError::EthereumBlockNotFound),
781 BlockId::Number(tag) => self
782 .block_by_number_or_tag(&tag)
783 .await?
784 .map(|block| block.block_hash())
785 .ok_or(ClientError::BlockNotFound),
786 }
787 }
788
789 pub async fn block_by_number_or_tag(
791 &self,
792 block: &BlockNumberOrTag,
793 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
794 match block {
795 BlockNumberOrTag::Number(n) => {
796 let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?;
797 self.block_by_number(n).await
798 },
799 BlockNumberOrTag::Finalized | BlockNumberOrTag::Safe => {
800 let block = self.block_provider.latest_finalized_block().await;
801 Ok(Some(block))
802 },
803 BlockNumberOrTag::Earliest => self.block_by_number(self.earliest_block_number()).await,
804 BlockNumberOrTag::Latest | BlockNumberOrTag::Pending => {
805 let block = self.block_provider.latest_block().await;
806 Ok(Some(block))
807 },
808 }
809 }
810
811 pub async fn block_for_tag(
816 &self,
817 at: BlockId,
818 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
819 match at {
820 BlockId::Hash(hash) => {
821 let Some(hash) = self.resolve_substrate_hash(&H256::from(hash.block_hash.0)).await
822 else {
823 return Ok(None);
824 };
825 self.block_by_hash(&hash).await
826 },
827 BlockId::Number(tag) => self.block_by_number_or_tag(&tag).await,
828 }
829 }
830
831 async fn resolve_tag_to_number(&self, tag: BlockNumberOrTag) -> Result<U256, ClientError> {
833 match tag {
834 BlockNumberOrTag::Number(n) => Ok(U256::from(n)),
835 BlockNumberOrTag::Earliest => Ok(U256::from(self.earliest_block_number())),
836 _ => Ok(self
837 .block_by_number_or_tag(&tag)
838 .await?
839 .ok_or(ClientError::BlockNotFound)?
840 .block_number()
841 .into()),
842 }
843 }
844
845 pub async fn storage_api(&self, block_hash: H256) -> Result<StorageApi, ClientError> {
847 Ok(StorageApi::new(self.api.at_block(block_hash).await?))
848 }
849
850 pub async fn runtime_api(
852 &self,
853 block_hash: H256,
854 ) -> Result<VersionAwareRuntimeApi, ClientError> {
855 self.runtime_api_provider.at(block_hash).await
856 }
857
858 pub async fn latest_finalized_block(&self) -> Arc<SubstrateBlock> {
860 self.block_provider.latest_finalized_block().await
861 }
862
863 pub async fn latest_block(&self) -> Arc<SubstrateBlock> {
865 self.block_provider.latest_block().await
866 }
867
868 async fn submit_transaction(
870 &self,
871 call: subxt::tx::StaticPayload<EthTransact>,
872 ) -> Result<StreamOfResults<TransactionStatus<SubstrateBlockHash>>, ClientError> {
873 let at_block = self.api.at_current_block().await?;
874 let ext = at_block.tx().create_unsigned(&call.unvalidated()).map_err(ClientError::from)?;
875
876 let sub = self
877 .rpc_client
878 .subscribe(
879 "author_submitAndWatchExtrinsic",
880 rpc_params![to_hex(ext.encoded())],
881 "author_unwatchExtrinsic",
882 )
883 .await?;
884
885 let sub = sub.map_err(|e| e.into());
886 Ok(StreamOf::new(Box::pin(sub)))
887 }
888
889 pub async fn submit(
891 &self,
892 call: subxt::tx::StaticPayload<EthTransact>,
893 ) -> Result<TransactionStatus<SubstrateBlockHash>, ClientError> {
894 let mut progress = self.submit_transaction(call).await.inspect_err(|err| {
895 log::debug!(target: LOG_TARGET, "Failed to submit transaction: {err:?}");
896 })?;
897
898 tokio::time::timeout(Duration::from_secs(5), async {
899 if let Some(status) = progress.next().await {
900 match status {
901 Ok(
902 tx @ (TransactionStatus::Future |
903 TransactionStatus::Ready |
904 TransactionStatus::Broadcast(_) |
907 TransactionStatus::InBlock(_) |
908 TransactionStatus::FinalityTimeout(_) |
909 TransactionStatus::Retracted(_) |
910 TransactionStatus::Finalized(_)),
911 ) => {
912 return Ok(tx);
913 },
914 Ok(
915 tx @ (TransactionStatus::Usurped(_) |
916 TransactionStatus::Dropped |
917 TransactionStatus::Invalid),
918 ) => {
919 return Err(ClientError::SubmitError(tx.into()));
920 },
921 Err(err) => {
922 log::debug!(target: LOG_TARGET, "Transaction submission failed: {err:?}");
923 return Err(ClientError::from(err));
924 },
925 }
926 }
927 return Err(ClientError::SubmitError(SubmitError::StreamEnded));
928 })
929 .await
930 .map_err(|_| ClientError::Timeout)?
931 }
932
933 pub async fn receipt(&self, tx_hash: &H256) -> Option<ReceiptInfo> {
935 self.receipt_provider.receipt_by_hash(tx_hash).await
936 }
937
938 pub async fn block_receipts(
942 &self,
943 at: BlockId,
944 ) -> Result<Option<Vec<ReceiptInfo>>, ClientError> {
945 let Some(block) = self.block_for_tag(at).await? else {
946 return Ok(None);
947 };
948 let receipts = self.receipt_provider.block_receipts(&block).await?;
949 Ok(Some(receipts.into_iter().map(|(_, receipt)| receipt).collect()))
950 }
951
952 pub async fn post_dispatch_weight(&self, tx_hash: &H256) -> Option<Weight> {
954 use crate::subxt_client::system::events::ExtrinsicSuccess;
955 let ReceiptInfo { block_hash, transaction_index, .. } = self.receipt(tx_hash).await?;
956 let block_hash = self.resolve_substrate_hash(&block_hash).await?;
957 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
958 let extrinsics = block.extrinsics().fetch().await.ok()?;
959 let ext = extrinsics.iter().nth(transaction_index.as_u32() as _)?.ok()?;
960 let event = ext.events().await.ok()?.find_first::<ExtrinsicSuccess>()?.ok()?;
961 Some(event.dispatch_info.weight.0)
962 }
963
964 pub async fn sync_state(
965 &self,
966 ) -> Result<sc_rpc::system::SyncState<SubstrateBlockNumber>, ClientError> {
967 let client = self.rpc_client.clone();
968 let sync_state: sc_rpc::system::SyncState<SubstrateBlockNumber> =
969 client.request("system_syncState", Default::default()).await?;
970 Ok(sync_state)
971 }
972
973 pub async fn syncing(&self) -> Result<SyncingStatus, ClientError> {
975 let health = self.rpc.system_health().await?;
976
977 let status = if health.is_syncing {
978 let sync_state = self.sync_state().await?;
979 SyncingStatus::SyncingProgress(SyncingProgress {
980 current_block: Some(sync_state.current_block.into()),
981 highest_block: Some(sync_state.highest_block.into()),
982 starting_block: Some(sync_state.starting_block.into()),
983 })
984 } else {
985 SyncingStatus::Bool(false)
986 };
987
988 Ok(status)
989 }
990
991 pub async fn receipt_by_hash_and_index(
993 &self,
994 block_hash: &H256,
995 transaction_index: usize,
996 ) -> Option<ReceiptInfo> {
997 self.receipt_provider
998 .receipt_by_block_hash_and_index(block_hash, transaction_index)
999 .await
1000 }
1001
1002 pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option<TransactionSigned> {
1003 self.receipt_provider.signed_tx_by_hash(tx_hash).await
1004 }
1005
1006 pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option<usize> {
1008 self.receipt_provider.receipts_count_per_block(block_hash).await
1009 }
1010
1011 pub async fn receipt_by_ethereum_hash_and_index(
1013 &self,
1014 ethereum_hash: &H256,
1015 transaction_index: usize,
1016 ) -> Option<ReceiptInfo> {
1017 let substrate_hash =
1019 self.resolve_substrate_hash(ethereum_hash).await.unwrap_or_else(|| {
1020 log::trace!(target: LOG_TARGET,
1021 "receipt_by_ethereum_hash_and_index: no ETH-to-substrate mapping for \
1022 {ethereum_hash:?}, falling back to substrate hash lookup");
1023 *ethereum_hash
1024 });
1025 self.receipt_by_hash_and_index(&substrate_hash, transaction_index).await
1026 }
1027
1028 pub async fn system_health(&self) -> Result<SystemHealth, ClientError> {
1030 let health = self.rpc.system_health().await?;
1031 Ok(health)
1032 }
1033
1034 pub async fn block_number(&self) -> Result<SubstrateBlockNumber, ClientError> {
1036 let latest_block = self.block_provider.latest_block().await;
1037 Ok(latest_block.block_number())
1038 }
1039
1040 pub async fn block_by_hash(
1042 &self,
1043 hash: &SubstrateBlockHash,
1044 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1045 self.block_provider.block_by_hash(hash).await
1046 }
1047
1048 pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option<H256> {
1051 self.receipt_provider.get_substrate_hash(ethereum_hash).await
1052 }
1053
1054 pub async fn resolve_ethereum_hash(&self, substrate_hash: &H256) -> Option<H256> {
1057 self.receipt_provider.get_ethereum_hash(substrate_hash).await
1058 }
1059
1060 pub async fn block_by_ethereum_hash(
1063 &self,
1064 ethereum_hash: &H256,
1065 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1066 if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
1068 return self.block_by_hash(&substrate_hash).await;
1069 }
1070
1071 log::trace!(target: LOG_TARGET,
1073 "block_by_ethereum_hash: no ETH-to-substrate mapping for {ethereum_hash:?}, \
1074 falling back to substrate hash lookup");
1075 self.block_by_hash(ethereum_hash).await
1076 }
1077
1078 pub async fn block_by_number(
1080 &self,
1081 block_number: SubstrateBlockNumber,
1082 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1083 self.block_provider.block_by_number(block_number).await
1084 }
1085
1086 pub async fn get_block_hash(
1088 &self,
1089 block_number: SubstrateBlockNumber,
1090 ) -> Result<Option<SubstrateBlockHash>, ClientError> {
1091 let maybe_block = self.block_provider.block_by_number(block_number).await?;
1092 Ok(maybe_block.map(|block| block.block_hash()))
1093 }
1094
1095 async fn tracing_block(
1096 &self,
1097 block_hash: H256,
1098 ) -> Result<
1099 sp_runtime::generic::Block<
1100 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
1101 sp_runtime::OpaqueExtrinsic,
1102 >,
1103 ClientError,
1104 > {
1105 let signed_block: Option<
1106 sp_runtime::generic::SignedBlock<
1107 sp_runtime::generic::Block<
1108 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
1109 sp_runtime::OpaqueExtrinsic,
1110 >,
1111 >,
1112 > = self.rpc_client.request("chain_getBlock", rpc_params![block_hash]).await?;
1113
1114 Ok(signed_block.ok_or(ClientError::BlockNotFound)?.block)
1115 }
1116
1117 pub async fn trace_block_by_number(
1119 &self,
1120 at: BlockNumberOrTag,
1121 config: TracerTypeV1,
1122 ) -> Result<Vec<TransactionTrace>, ClientError> {
1123 if self.receipt_provider.is_before_earliest_block(&at) {
1124 return Ok(vec![]);
1125 }
1126
1127 let block_hash = self.block_hash_for_tag(at.into()).await?;
1128 let block = self.tracing_block(block_hash).await?;
1129 let parent_hash = block.header().parent_hash;
1130 if parent_hash == Default::default() {
1132 return Ok(vec![]);
1133 }
1134
1135 let CallRecordedOutput { value: trace_entries, degraded } = self
1136 .runtime_api(parent_hash)
1137 .await?
1138 .trace_block(block, config, block_hash)
1139 .ok_or(ClientError::UnsupportedRuntimeApiMethod("trace_block"))?
1140 .await?;
1141
1142 let mut hashes = self
1143 .receipt_provider
1144 .block_transaction_hashes(&block_hash)
1145 .await
1146 .ok_or(ClientError::EthExtrinsicNotFound)?;
1147
1148 let mut entries = trace_entries
1149 .into_iter()
1150 .filter_map(|(index, entry)| {
1151 let index = index as usize;
1152 let tx_hash = hashes.remove(&index)?;
1153 let outcome = match entry {
1154 TraceEntry::Traced(trace) => TraceOutcome::Trace(trace),
1155 TraceEntry::NotTraced => {
1156 TraceOutcome::Error(ClientError::TraceUnavailable.to_string())
1157 },
1158 };
1159 Some((index, TransactionTrace { tx_hash, outcome }))
1160 })
1161 .collect::<Vec<_>>();
1162
1163 if degraded {
1164 entries.extend(hashes.into_iter().map(|(index, tx_hash)| {
1165 let outcome = TraceOutcome::Error(ClientError::TraceUnavailable.to_string());
1166 (index, TransactionTrace { tx_hash, outcome })
1167 }));
1168 entries.sort_unstable_by_key(|(index, _)| *index);
1169 }
1170
1171 Ok(entries.into_iter().map(|(_, entry)| entry).collect())
1172 }
1173
1174 pub async fn trace_transaction(
1176 &self,
1177 transaction_hash: H256,
1178 config: TracerTypeV1,
1179 ) -> Result<TraceV1, ClientError> {
1180 let (block_hash, transaction_index) = self
1181 .receipt_provider
1182 .find_transaction(&transaction_hash)
1183 .await
1184 .ok_or(ClientError::EthExtrinsicNotFound)?;
1185 let transaction_index = transaction_index as u32;
1186
1187 let block = self.tracing_block(block_hash).await?;
1188 let parent_hash = block.header.parent_hash;
1189 let CallRecordedOutput { value: entry, degraded } = self
1190 .runtime_api(parent_hash)
1191 .await?
1192 .trace_tx(block, transaction_index, config, block_hash)
1193 .ok_or(ClientError::UnsupportedRuntimeApiMethod("trace_tx"))?
1194 .await?;
1195
1196 match entry {
1197 Some(TraceEntry::Traced(trace)) => Ok(trace),
1198 Some(TraceEntry::NotTraced) => Err(ClientError::TraceUnavailable),
1199 None if degraded => Err(ClientError::TraceUnavailable),
1200 None => Err(ClientError::EthExtrinsicNotFound),
1201 }
1202 }
1203
1204 pub async fn trace_call(
1206 &self,
1207 transaction: GenericTransactionV1,
1208 block: BlockId,
1209 config: TracerTypeV1,
1210 state_overrides: Option<StateOverrideSetV1>,
1211 ) -> Result<TraceV1, ClientError> {
1212 let block_hash = self.block_hash_for_tag(block).await?;
1213 let runtime_api = self.runtime_api(block_hash).await?;
1214 runtime_api
1215 .trace_call(transaction, config, state_overrides)
1216 .ok_or(ClientError::UnsupportedRuntimeApiMethod("trace_call"))?
1217 .await
1218 }
1219
1220 pub async fn evm_block(
1222 &self,
1223 block: Arc<SubstrateBlock>,
1224 hydrated_transactions: bool,
1225 ) -> Option<BlockV1> {
1226 log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.block_hash());
1227
1228 if self
1229 .receipt_provider
1230 .is_before_earliest_block(&BlockNumberOrTag::Number(block.block_number().into()))
1231 {
1232 log::trace!(target: LOG_TARGET,
1233 "Block #{} is before receipt floor, skipping", block.block_number());
1234 return None;
1235 }
1236
1237 let eth_block_result = async {
1243 self.runtime_api(block.block_hash())
1244 .await?
1245 .eth_block()
1246 .ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_block"))?
1247 .await
1248 }
1249 .await;
1250 match eth_block_result {
1251 Ok(mut eth_block) => {
1252 log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash);
1253
1254 if hydrated_transactions {
1255 let tx_infos = self
1257 .receipt_provider
1258 .receipts_from_block(&block, eth_block.hash)
1259 .await
1260 .inspect_err(|err| {
1261 log::trace!(target: LOG_TARGET,
1262 "Failed to extract receipts for block #{}: {err:?}",
1263 block.block_number());
1264 })
1265 .unwrap_or_default()
1266 .into_iter()
1267 .map(|(signed_tx, receipt)| receipt.transaction_info(signed_tx))
1268 .collect::<Vec<_>>();
1269
1270 eth_block.transactions = HashesOrTransactionInfosV1::TransactionInfos(tx_infos);
1271 }
1272
1273 Some(eth_block)
1274 },
1275 Err(err) => {
1276 log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.block_hash());
1277 None
1278 },
1279 }
1280 }
1281
1282 pub fn chain_id(&self) -> u64 {
1284 self.chain_id
1285 }
1286
1287 pub fn max_block_weight(&self) -> Weight {
1289 self.max_block_weight
1290 }
1291
1292 pub fn block_notifier(&self) -> Option<tokio::sync::broadcast::Sender<H256>> {
1294 self.block_notifier.clone()
1295 }
1296
1297 pub async fn logs(&self, filter: Option<Filter>) -> Result<Vec<Log>, ClientError> {
1299 let logs =
1300 self.receipt_provider
1301 .logs(filter, |tag| async move {
1302 self.resolve_tag_to_number(tag).await.map_err(Into::into)
1303 })
1304 .await
1305 .map_err(ClientError::LogFilterFailed)?;
1306
1307 Ok(logs)
1308 }
1309
1310 pub async fn fee_history(
1311 &self,
1312 block_count: u32,
1313 latest_block: BlockNumberOrTag,
1314 reward_percentiles: Option<Vec<f64>>,
1315 ) -> Result<FeeHistoryResult, ClientError> {
1316 let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else {
1317 return Err(ClientError::BlockNotFound);
1318 };
1319
1320 self.fee_history_provider
1321 .fee_history(block_count, latest_block.block_number(), reward_percentiles)
1322 .await
1323 }
1324
1325 pub fn is_automine(&self) -> bool {
1327 self.automine
1328 }
1329
1330 pub async fn get_automine(&self) -> bool {
1332 get_automine(&self.rpc_client).await
1333 }
1334
1335 pub fn get_block_subscription_rx(&self) -> tokio::sync::broadcast::Receiver<BlockV1> {
1337 self.block_subscription_tx.subscribe()
1338 }
1339
1340 pub fn get_log_subscription_rx(&self) -> tokio::sync::broadcast::Receiver<Log> {
1342 self.log_subscription_tx.subscribe()
1343 }
1344}
1345
1346fn to_hex(bytes: impl AsRef<[u8]>) -> String {
1347 format!("0x{}", hex::encode(bytes.as_ref()))
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352 use super::*;
1353 use sc_rpc_api::state::error::{
1354 CALL_RECORDED_DENIED_ERROR_CODE, CALL_RECORDED_UNSUPPORTED_ERROR_CODE,
1355 };
1356 use subxt::rpcs::UserError;
1357
1358 fn rpc_user_error(code: i32) -> ClientError {
1359 ClientError::RpcError(subxt::rpcs::Error::User(UserError {
1360 code,
1361 message: "..".to_string(),
1362 data: None,
1363 }))
1364 }
1365
1366 #[test]
1367 fn missing_method_is_version_skew() {
1368 assert_eq!(
1369 rpc_user_error(-32601).recorded_unavailable_reason(),
1370 Some(RecordedUnavailable::MethodMissing),
1371 );
1372 }
1373
1374 #[test]
1375 fn unsafe_denied_is_a_fallback_reason() {
1376 assert_eq!(
1377 rpc_user_error(CALL_RECORDED_DENIED_ERROR_CODE).recorded_unavailable_reason(),
1378 Some(RecordedUnavailable::Denied),
1379 );
1380 }
1381
1382 #[test]
1383 fn no_recorder_code_is_expected() {
1384 assert_eq!(
1385 rpc_user_error(CALL_RECORDED_UNSUPPORTED_ERROR_CODE).recorded_unavailable_reason(),
1386 Some(RecordedUnavailable::NoRecorder),
1387 );
1388 }
1389
1390 #[test]
1391 fn genuine_errors_do_not_trigger_fallback() {
1392 assert!(rpc_user_error(-32000).recorded_unavailable_reason().is_none());
1393 assert!(rpc_user_error(-32602).recorded_unavailable_reason().is_none());
1394 assert!(ClientError::EthExtrinsicNotFound.recorded_unavailable_reason().is_none());
1395 assert!(ClientError::BlockNotFound.recorded_unavailable_reason().is_none());
1396 }
1397}