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 chain_id(api: &OnlineClient<SrcChainConfig>) -> Result<u64, ClientError> {
411 let query = subxt_client::constants().revive().chain_id().unvalidated();
412 let at_block = api.at_current_block().await?;
413 at_block.constants().entry(query).map_err(|err| err.into())
414}
415
416async fn max_block_weight(api: &OnlineClient<SrcChainConfig>) -> Result<Weight, ClientError> {
418 let query = subxt_client::constants().system().block_weights().unvalidated();
419 let at_block = api.at_current_block().await?;
420 let weights = at_block.constants().entry(query)?;
421 let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block);
422 Ok(max_block.0)
423}
424
425async fn get_automine(rpc_client: &RpcClient) -> bool {
427 match rpc_client.request::<bool>("getAutomine", rpc_params![]).await {
428 Ok(val) => val,
429 Err(err) => {
430 log::info!(target: LOG_TARGET, "Node does not have getAutomine RPC. Defaulting to automine=false. error: {err:?}");
431 false
432 },
433 }
434}
435
436pub async fn connect(
439 node_rpc_url: &str,
440 max_request_size: u32,
441 max_response_size: u32,
442) -> Result<
443 (OnlineClient<SrcChainConfig>, RpcClient, LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>),
444 ClientError,
445> {
446 log::info!(target: LOG_TARGET, "๐ Connecting to node at: {node_rpc_url} ...");
447 let rpc_client = ReconnectingRpcClient::builder()
448 .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10)))
449 .max_request_size(max_request_size)
450 .max_response_size(max_response_size)
451 .build(node_rpc_url.to_string())
452 .await?;
453 let rpc_client = RpcClient::new(rpc_client);
454 log::info!(target: LOG_TARGET, "๐ Connected to node at: {node_rpc_url}");
455
456 let backend = Arc::new(LegacyBackend::builder().build(rpc_client.clone()));
461 let api = OnlineClient::<SrcChainConfig>::from_backend(backend).await?;
462 let rpc = LegacyRpcMethods::<RpcConfigFor<SrcChainConfig>>::new(rpc_client.clone());
463 Ok((api, rpc_client, rpc))
464}
465
466impl Client {
467 pub(crate) async fn new(
469 api: OnlineClient<SrcChainConfig>,
470 rpc_client: RpcClient,
471 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
472 block_provider: SubxtBlockInfoProvider,
473 receipt_provider: ReceiptProvider,
474 is_archive: bool,
475 subscription_gap_queue: SubscriptionGapQueue,
476 runtime_api_provider: VersionAwareRuntimeApiProvider,
477 ) -> Result<Self, ClientError> {
478 let (chain_id, max_block_weight, automine) =
479 tokio::try_join!(chain_id(&api), max_block_weight(&api), async {
480 Ok(get_automine(&rpc_client).await)
481 },)?;
482
483 let latest_finalized_block = block_provider.latest_finalized_block().await;
486 runtime_api_provider.at(latest_finalized_block.block_hash()).await?;
487
488 if !is_archive {
491 if let Some(known) = known_first_evm_block_for_chain(chain_id) {
492 let best = block_provider.latest_block_number().await;
493 if known > best {
494 log::debug!(
495 target: LOG_TARGET,
496 "Hardcoded first EVM block {known} exceeds best block {best} \
497 for chain {chain_id}, defaulting to 0"
498 );
499 receipt_provider.set_first_evm_block(0).await?;
500 }
501 }
502 }
503
504 let client = Self {
505 api,
506 rpc_client,
507 rpc,
508 receipt_provider,
509 block_provider,
510 fee_history_provider: FeeHistoryProvider::default(),
511 chain_id,
512 max_block_weight,
513 automine,
514 block_notifier: automine
515 .then(|| tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0),
516 subscription_lock: Arc::new(Mutex::new(())),
517 block_subscription_tx: tokio::sync::broadcast::channel(256).0,
518 log_subscription_tx: tokio::sync::broadcast::channel(1000).0,
519 is_archive,
520 backfill_complete: Arc::new(AtomicBool::new(false)),
521 subscription_gap_queue,
522 runtime_api_provider,
523 };
524
525 Ok(client)
526 }
527
528 pub(crate) fn mark_backfill_complete(&self) {
530 self.backfill_complete.store(true, Ordering::Release);
531 }
532
533 async fn advance_sync_head(&self, block_number: SubstrateBlockNumber, hash: H256) {
536 if !self.is_archive ||
537 !self.backfill_complete.load(Ordering::Acquire) ||
538 self.subscription_gap_queue.has_pending()
539 {
540 return;
541 }
542
543 if let Err(err) = self
544 .receipt_provider
545 .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(block_number, hash))
546 .await
547 {
548 log::warn!(target: LOG_TARGET, "Failed to advance sync head: {err:?}");
549 }
550 }
551
552 pub fn create_block_notifier(&mut self) {
554 self.block_notifier = Some(tokio::sync::broadcast::channel::<H256>(NOTIFIER_CAPACITY).0);
555 }
556
557 pub fn set_block_notifier(&mut self, notifier: Option<tokio::sync::broadcast::Sender<H256>>) {
559 self.block_notifier = notifier;
560 }
561
562 pub(crate) fn api(&self) -> &OnlineClient<SrcChainConfig> {
563 &self.api
564 }
565
566 pub(crate) fn receipt_provider(&self) -> &ReceiptProvider {
567 &self.receipt_provider
568 }
569
570 pub(crate) fn block_provider(&self) -> &SubxtBlockInfoProvider {
571 &self.block_provider
572 }
573
574 pub(crate) fn subscription_gap_queue(&self) -> &SubscriptionGapQueue {
575 &self.subscription_gap_queue
576 }
577
578 fn earliest_block_number(&self) -> SubstrateBlockNumber {
581 self.receipt_provider
582 .first_evm_block()
583 .or_else(|| known_first_evm_block_for_chain(self.chain_id))
584 .unwrap_or(0)
585 }
586
587 async fn subscribe_new_blocks<F, Fut>(
589 &self,
590 subscription_type: SubscriptionType,
591 callback: F,
592 ) -> Result<(), ClientError>
593 where
594 F: Fn(SubstrateBlock) -> Fut + Send + Sync,
595 Fut: std::future::Future<Output = Result<(), ClientError>> + Send,
596 {
597 let mut block_stream = match subscription_type {
598 SubscriptionType::BestBlocks => self.api.stream_best_blocks().await,
599 SubscriptionType::FinalizedBlocks => self.api.stream_blocks().await,
600 }
601 .inspect_err(|err| {
602 log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}");
603 })?;
604
605 let mut last_finalized_seen: Option<SubstrateBlockNumber> = None;
606
607 while let Some(block) = block_stream.next().await {
608 let block = match block {
609 Ok(block) => block,
610 Err(err) => {
611 let err: subxt::Error = err.into();
612 if err.is_disconnected_will_reconnect() {
613 log::warn!(
614 target: LOG_TARGET,
615 "The RPC connection was lost and we may have missed a few blocks \
616 ({subscription_type:?}, last finalized: {last_finalized_seen:?}): {err:?}"
617 );
618 continue;
619 }
620
621 log::error!(target: LOG_TARGET, "Failed to fetch block ({subscription_type:?}): {err:?}");
622 return Err(err.into());
623 },
624 };
625
626 let block = match block.at().await {
631 Ok(block) => block,
632 Err(err) => {
633 log::warn!(
634 target: LOG_TARGET,
635 "Failed to resolve streamed {subscription_type:?} block #{} ({:?}), skipping: {err:?}",
636 block.number(),
637 block.hash(),
638 );
639 continue;
640 },
641 };
642
643 let _guard = self.subscription_lock.lock().await;
645
646 let block_number = block.block_number();
647
648 if subscription_type == SubscriptionType::FinalizedBlocks {
650 if let Some(last) = last_finalized_seen {
651 self.subscription_gap_queue.detect_and_queue(block_number, last);
652 }
653 last_finalized_seen = Some(block_number);
655 }
656
657 log::trace!(target: LOG_TARGET_SUBSCRIPTION, "โณ Processing {subscription_type:?} block: {block_number}");
658 if let Err(err) = callback(block).await {
659 log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}");
660 } else {
661 log::trace!(target: LOG_TARGET_SUBSCRIPTION, "โ
Processed {subscription_type:?} block: {block_number}");
662 }
663 }
664
665 log::info!(target: LOG_TARGET, "Block subscription ended");
666 Ok(())
667 }
668
669 async fn process_block(
671 &self,
672 block: &SubstrateBlock,
673 ) -> Result<(BlockV1, Vec<ReceiptInfo>), ClientError> {
674 let block_number = block.block_number();
675 let hash = block.block_hash();
676
677 macro_rules! time {
678 ($label:expr, $expr:expr) => {{
679 let t = std::time::Instant::now();
680 let r = $expr;
681 log::trace!(
682 target: LOG_TARGET,
683 "โฑ๏ธ #{block_number} {}: {:?}",
684 $label, t.elapsed(),
685 );
686 r
687 }};
688 }
689
690 let eth_block = time!(
691 "eth_block",
692 self.runtime_api(hash)
693 .await?
694 .eth_block()
695 .ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_block"))?
696 .await?
697 );
698 let receipts = time!(
699 "receipts_from_block",
700 self.receipt_provider.receipts_from_block(block, eth_block.hash).await?
701 );
702 time!(
703 "insert_block_receipts",
704 self.receipt_provider
705 .insert_block_receipts(block, &receipts, ð_block.hash)
706 .await?
707 );
708
709 let (_, receipt_infos): (Vec<_>, Vec<_>) = receipts.into_iter().unzip();
710 self.fee_history_provider.update_fee_history(ð_block, &receipt_infos).await;
711
712 Ok((eth_block, receipt_infos))
713 }
714
715 pub async fn subscribe_and_cache_new_blocks(
717 &self,
718 subscription_type: SubscriptionType,
719 ) -> Result<(), ClientError> {
720 log::info!(target: LOG_TARGET, "๐ Subscribing to new blocks ({subscription_type:?})");
721 self.subscribe_new_blocks(subscription_type, |block| async {
722 let hash = block.block_hash();
723
724 match subscription_type {
725 SubscriptionType::BestBlocks => {
726 let (eth_block, _) = self.process_block(&block).await?;
727 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
728
729 if let Some(sender) = &self.block_notifier {
730 if sender.receiver_count() > 0 {
731 let _ = sender.send(hash);
732 }
733 }
734 if self.block_subscription_tx.receiver_count() > 0 {
735 let _ = self.block_subscription_tx.send(eth_block);
736 }
737 },
738 SubscriptionType::FinalizedBlocks => {
739 let block_number = block.block_number();
740 let (receipt_infos, eth_hash) = match self
741 .receipt_provider
742 .get_processed_eth_block_hash(block_number, hash)
743 .await
744 {
745 Some(eth_hash) => {
746 log::trace!(target: LOG_TARGET_SUBSCRIPTION,
747 "โฉ Finalized block #{block_number} already processed, \
748 skipping extraction");
749 (None, eth_hash)
750 },
751 None => {
752 let (eth_block, infos) = self.process_block(&block).await?;
753 (Some(infos), eth_block.hash)
754 },
755 };
756
757 self.block_provider.update_latest(Arc::new(block), subscription_type).await;
758 self.advance_sync_head(block_number, hash).await;
759
760 if self.log_subscription_tx.receiver_count() > 0 {
761 let logs = match receipt_infos {
762 Some(infos) => infos.into_iter().flat_map(|r| r.logs).collect(),
763 None => {
764 self.receipt_provider
765 .logs_by_block_number(block_number, eth_hash)
766 .await?
767 },
768 };
769 for log in logs {
770 let _ = self.log_subscription_tx.send(log);
771 }
772 }
773 },
774 }
775
776 Ok(())
777 })
778 .await
779 }
780
781 pub async fn block_hash_for_tag(&self, at: BlockId) -> Result<SubstrateBlockHash, ClientError> {
783 match at {
784 BlockId::Hash(hash) => self
785 .resolve_substrate_hash(&H256::from(hash.block_hash.0))
786 .await
787 .ok_or(ClientError::EthereumBlockNotFound),
788 BlockId::Number(tag) => self
789 .block_by_number_or_tag(&tag)
790 .await?
791 .map(|block| block.block_hash())
792 .ok_or(ClientError::BlockNotFound),
793 }
794 }
795
796 pub async fn block_by_number_or_tag(
798 &self,
799 block: &BlockNumberOrTag,
800 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
801 match block {
802 BlockNumberOrTag::Number(n) => {
803 let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?;
804 self.block_by_number(n).await
805 },
806 BlockNumberOrTag::Finalized | BlockNumberOrTag::Safe => {
807 let block = self.block_provider.latest_finalized_block().await;
808 Ok(Some(block))
809 },
810 BlockNumberOrTag::Earliest => self.block_by_number(self.earliest_block_number()).await,
811 BlockNumberOrTag::Latest | BlockNumberOrTag::Pending => {
812 let block = self.block_provider.latest_block().await;
813 Ok(Some(block))
814 },
815 }
816 }
817
818 pub async fn block_for_tag(
823 &self,
824 at: BlockId,
825 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
826 match at {
827 BlockId::Hash(hash) => {
828 let Some(hash) = self.resolve_substrate_hash(&H256::from(hash.block_hash.0)).await
829 else {
830 return Ok(None);
831 };
832 self.block_by_hash(&hash).await
833 },
834 BlockId::Number(tag) => self.block_by_number_or_tag(&tag).await,
835 }
836 }
837
838 async fn resolve_tag_to_number(&self, tag: BlockNumberOrTag) -> Result<U256, ClientError> {
840 match tag {
841 BlockNumberOrTag::Number(n) => Ok(U256::from(n)),
842 BlockNumberOrTag::Earliest => Ok(U256::from(self.earliest_block_number())),
843 _ => Ok(self
844 .block_by_number_or_tag(&tag)
845 .await?
846 .ok_or(ClientError::BlockNotFound)?
847 .block_number()
848 .into()),
849 }
850 }
851
852 pub async fn storage_api(&self, block_hash: H256) -> Result<StorageApi, ClientError> {
854 Ok(StorageApi::new(self.api.at_block(block_hash).await?))
855 }
856
857 pub async fn runtime_api(
859 &self,
860 block_hash: H256,
861 ) -> Result<VersionAwareRuntimeApi, ClientError> {
862 self.runtime_api_provider.at(block_hash).await
863 }
864
865 pub async fn latest_finalized_block(&self) -> Arc<SubstrateBlock> {
867 self.block_provider.latest_finalized_block().await
868 }
869
870 pub async fn latest_block(&self) -> Arc<SubstrateBlock> {
872 self.block_provider.latest_block().await
873 }
874
875 async fn submit_transaction(
877 &self,
878 call: subxt::tx::StaticPayload<EthTransact>,
879 ) -> Result<StreamOfResults<TransactionStatus<SubstrateBlockHash>>, ClientError> {
880 let at_block = self.api.at_current_block().await?;
881 let ext = at_block.tx().create_unsigned(&call.unvalidated()).map_err(ClientError::from)?;
882
883 let sub = self
884 .rpc_client
885 .subscribe(
886 "author_submitAndWatchExtrinsic",
887 rpc_params![to_hex(ext.encoded())],
888 "author_unwatchExtrinsic",
889 )
890 .await?;
891
892 let sub = sub.map_err(|e| e.into());
893 Ok(StreamOf::new(Box::pin(sub)))
894 }
895
896 pub async fn submit(
898 &self,
899 call: subxt::tx::StaticPayload<EthTransact>,
900 ) -> Result<TransactionStatus<SubstrateBlockHash>, ClientError> {
901 let mut progress = self.submit_transaction(call).await.inspect_err(|err| {
902 log::debug!(target: LOG_TARGET, "Failed to submit transaction: {err:?}");
903 })?;
904
905 tokio::time::timeout(Duration::from_secs(5), async {
906 if let Some(status) = progress.next().await {
907 match status {
908 Ok(
909 tx @ (TransactionStatus::Future |
910 TransactionStatus::Ready |
911 TransactionStatus::Broadcast(_) |
914 TransactionStatus::InBlock(_) |
915 TransactionStatus::FinalityTimeout(_) |
916 TransactionStatus::Retracted(_) |
917 TransactionStatus::Finalized(_)),
918 ) => {
919 return Ok(tx);
920 },
921 Ok(
922 tx @ (TransactionStatus::Usurped(_) |
923 TransactionStatus::Dropped |
924 TransactionStatus::Invalid),
925 ) => {
926 return Err(ClientError::SubmitError(tx.into()));
927 },
928 Err(err) => {
929 log::debug!(target: LOG_TARGET, "Transaction submission failed: {err:?}");
930 return Err(ClientError::from(err));
931 },
932 }
933 }
934 return Err(ClientError::SubmitError(SubmitError::StreamEnded));
935 })
936 .await
937 .map_err(|_| ClientError::Timeout)?
938 }
939
940 pub async fn receipt(&self, tx_hash: &H256) -> Option<ReceiptInfo> {
942 self.receipt_provider.receipt_by_hash(tx_hash).await
943 }
944
945 pub async fn block_receipts(
949 &self,
950 at: BlockId,
951 ) -> Result<Option<Vec<ReceiptInfo>>, ClientError> {
952 let Some(block) = self.block_for_tag(at).await? else {
953 return Ok(None);
954 };
955 let receipts = self.receipt_provider.block_receipts(&block).await?;
956 Ok(Some(receipts.into_iter().map(|(_, receipt)| receipt).collect()))
957 }
958
959 pub async fn post_dispatch_weight(&self, tx_hash: &H256) -> Option<Weight> {
961 use crate::subxt_client::system::events::ExtrinsicSuccess;
962 let ReceiptInfo { block_hash, transaction_index, .. } = self.receipt(tx_hash).await?;
963 let block_hash = self.resolve_substrate_hash(&block_hash).await?;
964 let block = self.block_provider.block_by_hash(&block_hash).await.ok()??;
965 let extrinsics = block.extrinsics().fetch().await.ok()?;
966 let ext = extrinsics.iter().nth(transaction_index.as_u32() as _)?.ok()?;
967 let event = ext.events().await.ok()?.find_first::<ExtrinsicSuccess>()?.ok()?;
968 Some(event.dispatch_info.weight.0)
969 }
970
971 pub async fn sync_state(
972 &self,
973 ) -> Result<sc_rpc::system::SyncState<SubstrateBlockNumber>, ClientError> {
974 let client = self.rpc_client.clone();
975 let sync_state: sc_rpc::system::SyncState<SubstrateBlockNumber> =
976 client.request("system_syncState", Default::default()).await?;
977 Ok(sync_state)
978 }
979
980 pub async fn syncing(&self) -> Result<SyncingStatus, ClientError> {
982 let health = self.rpc.system_health().await?;
983
984 let status = if health.is_syncing {
985 let sync_state = self.sync_state().await?;
986 SyncingStatus::SyncingProgress(SyncingProgress {
987 current_block: Some(sync_state.current_block.into()),
988 highest_block: Some(sync_state.highest_block.into()),
989 starting_block: Some(sync_state.starting_block.into()),
990 })
991 } else {
992 SyncingStatus::Bool(false)
993 };
994
995 Ok(status)
996 }
997
998 pub async fn receipt_by_hash_and_index(
1000 &self,
1001 block_hash: &H256,
1002 transaction_index: usize,
1003 ) -> Option<ReceiptInfo> {
1004 self.receipt_provider
1005 .receipt_by_block_hash_and_index(block_hash, transaction_index)
1006 .await
1007 }
1008
1009 pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option<TransactionSigned> {
1010 self.receipt_provider.signed_tx_by_hash(tx_hash).await
1011 }
1012
1013 pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option<usize> {
1015 self.receipt_provider.receipts_count_per_block(block_hash).await
1016 }
1017
1018 pub async fn receipt_by_ethereum_hash_and_index(
1020 &self,
1021 ethereum_hash: &H256,
1022 transaction_index: usize,
1023 ) -> Option<ReceiptInfo> {
1024 let substrate_hash =
1026 self.resolve_substrate_hash(ethereum_hash).await.unwrap_or_else(|| {
1027 log::trace!(target: LOG_TARGET,
1028 "receipt_by_ethereum_hash_and_index: no ETH-to-substrate mapping for \
1029 {ethereum_hash:?}, falling back to substrate hash lookup");
1030 *ethereum_hash
1031 });
1032 self.receipt_by_hash_and_index(&substrate_hash, transaction_index).await
1033 }
1034
1035 pub async fn system_health(&self) -> Result<SystemHealth, ClientError> {
1037 let health = self.rpc.system_health().await?;
1038 Ok(health)
1039 }
1040
1041 pub async fn block_number(&self) -> Result<SubstrateBlockNumber, ClientError> {
1043 let latest_block = self.block_provider.latest_block().await;
1044 Ok(latest_block.block_number())
1045 }
1046
1047 pub async fn block_by_hash(
1049 &self,
1050 hash: &SubstrateBlockHash,
1051 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1052 self.block_provider.block_by_hash(hash).await
1053 }
1054
1055 pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option<H256> {
1058 self.receipt_provider.get_substrate_hash(ethereum_hash).await
1059 }
1060
1061 pub async fn resolve_ethereum_hash(&self, substrate_hash: &H256) -> Option<H256> {
1064 self.receipt_provider.get_ethereum_hash(substrate_hash).await
1065 }
1066
1067 pub async fn block_by_ethereum_hash(
1070 &self,
1071 ethereum_hash: &H256,
1072 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1073 if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
1075 return self.block_by_hash(&substrate_hash).await;
1076 }
1077
1078 log::trace!(target: LOG_TARGET,
1080 "block_by_ethereum_hash: no ETH-to-substrate mapping for {ethereum_hash:?}, \
1081 falling back to substrate hash lookup");
1082 self.block_by_hash(ethereum_hash).await
1083 }
1084
1085 pub async fn block_by_number(
1087 &self,
1088 block_number: SubstrateBlockNumber,
1089 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
1090 self.block_provider.block_by_number(block_number).await
1091 }
1092
1093 pub async fn get_block_hash(
1095 &self,
1096 block_number: SubstrateBlockNumber,
1097 ) -> Result<Option<SubstrateBlockHash>, ClientError> {
1098 let maybe_block = self.block_provider.block_by_number(block_number).await?;
1099 Ok(maybe_block.map(|block| block.block_hash()))
1100 }
1101
1102 async fn tracing_block(
1103 &self,
1104 block_hash: H256,
1105 ) -> Result<
1106 sp_runtime::generic::Block<
1107 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
1108 sp_runtime::OpaqueExtrinsic,
1109 >,
1110 ClientError,
1111 > {
1112 let signed_block: Option<
1113 sp_runtime::generic::SignedBlock<
1114 sp_runtime::generic::Block<
1115 sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>,
1116 sp_runtime::OpaqueExtrinsic,
1117 >,
1118 >,
1119 > = self.rpc_client.request("chain_getBlock", rpc_params![block_hash]).await?;
1120
1121 Ok(signed_block.ok_or(ClientError::BlockNotFound)?.block)
1122 }
1123
1124 pub async fn trace_block_by_number(
1126 &self,
1127 at: BlockNumberOrTag,
1128 config: TracerTypeV1,
1129 ) -> Result<Vec<TransactionTrace>, ClientError> {
1130 if self.receipt_provider.is_before_earliest_block(&at) {
1131 return Ok(vec![]);
1132 }
1133
1134 let block_hash = self.block_hash_for_tag(at.into()).await?;
1135 let block = self.tracing_block(block_hash).await?;
1136 let parent_hash = block.header().parent_hash;
1137 if parent_hash == Default::default() {
1139 return Ok(vec![]);
1140 }
1141
1142 let CallRecordedOutput { value: trace_entries, degraded } = self
1143 .runtime_api(parent_hash)
1144 .await?
1145 .trace_block(block, config, block_hash)
1146 .ok_or(ClientError::UnsupportedRuntimeApiMethod("trace_block"))?
1147 .await?;
1148
1149 let mut hashes = self
1150 .receipt_provider
1151 .block_transaction_hashes(&block_hash)
1152 .await
1153 .ok_or(ClientError::EthExtrinsicNotFound)?;
1154
1155 let mut entries = trace_entries
1156 .into_iter()
1157 .filter_map(|(index, entry)| {
1158 let index = index as usize;
1159 let tx_hash = hashes.remove(&index)?;
1160 let outcome = match entry {
1161 TraceEntry::Traced(trace) => TraceOutcome::Trace(trace),
1162 TraceEntry::NotTraced => {
1163 TraceOutcome::Error(ClientError::TraceUnavailable.to_string())
1164 },
1165 };
1166 Some((index, TransactionTrace { tx_hash, outcome }))
1167 })
1168 .collect::<Vec<_>>();
1169
1170 if degraded {
1171 entries.extend(hashes.into_iter().map(|(index, tx_hash)| {
1172 let outcome = TraceOutcome::Error(ClientError::TraceUnavailable.to_string());
1173 (index, TransactionTrace { tx_hash, outcome })
1174 }));
1175 entries.sort_unstable_by_key(|(index, _)| *index);
1176 }
1177
1178 Ok(entries.into_iter().map(|(_, entry)| entry).collect())
1179 }
1180
1181 pub async fn trace_transaction(
1183 &self,
1184 transaction_hash: H256,
1185 config: TracerTypeV1,
1186 ) -> Result<TraceV1, ClientError> {
1187 let (block_hash, transaction_index) = self
1188 .receipt_provider
1189 .find_transaction(&transaction_hash)
1190 .await
1191 .ok_or(ClientError::EthExtrinsicNotFound)?;
1192 let transaction_index = transaction_index as u32;
1193
1194 let block = self.tracing_block(block_hash).await?;
1195 let parent_hash = block.header.parent_hash;
1196 let CallRecordedOutput { value: entry, degraded } = self
1197 .runtime_api(parent_hash)
1198 .await?
1199 .trace_tx(block, transaction_index, config, block_hash)
1200 .ok_or(ClientError::UnsupportedRuntimeApiMethod("trace_tx"))?
1201 .await?;
1202
1203 match entry {
1204 Some(TraceEntry::Traced(trace)) => Ok(trace),
1205 Some(TraceEntry::NotTraced) => Err(ClientError::TraceUnavailable),
1206 None if degraded => Err(ClientError::TraceUnavailable),
1207 None => Err(ClientError::EthExtrinsicNotFound),
1208 }
1209 }
1210
1211 pub async fn trace_call(
1213 &self,
1214 transaction: GenericTransactionV1,
1215 block: BlockId,
1216 config: TracerTypeV1,
1217 state_overrides: Option<StateOverrideSetV1>,
1218 ) -> Result<TraceV1, ClientError> {
1219 let block_hash = self.block_hash_for_tag(block).await?;
1220 let runtime_api = self.runtime_api(block_hash).await?;
1221 runtime_api
1222 .trace_call(transaction, config, state_overrides)
1223 .ok_or(ClientError::UnsupportedRuntimeApiMethod("trace_call"))?
1224 .await
1225 }
1226
1227 pub async fn evm_block(
1229 &self,
1230 block: Arc<SubstrateBlock>,
1231 hydrated_transactions: bool,
1232 ) -> Option<BlockV1> {
1233 log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.block_hash());
1234
1235 if self
1236 .receipt_provider
1237 .is_before_earliest_block(&BlockNumberOrTag::Number(block.block_number().into()))
1238 {
1239 log::trace!(target: LOG_TARGET,
1240 "Block #{} is before receipt floor, skipping", block.block_number());
1241 return None;
1242 }
1243
1244 let eth_block_result = async {
1250 self.runtime_api(block.block_hash())
1251 .await?
1252 .eth_block()
1253 .ok_or(ClientError::UnsupportedRuntimeApiMethod("eth_block"))?
1254 .await
1255 }
1256 .await;
1257 match eth_block_result {
1258 Ok(mut eth_block) => {
1259 log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash);
1260
1261 if hydrated_transactions {
1262 let tx_infos = self
1264 .receipt_provider
1265 .receipts_from_block(&block, eth_block.hash)
1266 .await
1267 .inspect_err(|err| {
1268 log::trace!(target: LOG_TARGET,
1269 "Failed to extract receipts for block #{}: {err:?}",
1270 block.block_number());
1271 })
1272 .unwrap_or_default()
1273 .into_iter()
1274 .map(|(signed_tx, receipt)| receipt.transaction_info(signed_tx))
1275 .collect::<Vec<_>>();
1276
1277 eth_block.transactions = HashesOrTransactionInfosV1::TransactionInfos(tx_infos);
1278 }
1279
1280 Some(eth_block)
1281 },
1282 Err(err) => {
1283 log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.block_hash());
1284 None
1285 },
1286 }
1287 }
1288
1289 pub fn chain_id(&self) -> u64 {
1291 self.chain_id
1292 }
1293
1294 pub fn max_block_weight(&self) -> Weight {
1296 self.max_block_weight
1297 }
1298
1299 pub fn block_notifier(&self) -> Option<tokio::sync::broadcast::Sender<H256>> {
1301 self.block_notifier.clone()
1302 }
1303
1304 pub async fn logs(&self, filter: Option<Filter>) -> Result<Vec<Log>, ClientError> {
1306 let logs =
1307 self.receipt_provider
1308 .logs(filter, |tag| async move {
1309 self.resolve_tag_to_number(tag).await.map_err(Into::into)
1310 })
1311 .await
1312 .map_err(ClientError::LogFilterFailed)?;
1313
1314 Ok(logs)
1315 }
1316
1317 pub async fn fee_history(
1318 &self,
1319 block_count: u32,
1320 latest_block: BlockNumberOrTag,
1321 reward_percentiles: Option<Vec<f64>>,
1322 ) -> Result<FeeHistoryResult, ClientError> {
1323 let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else {
1324 return Err(ClientError::BlockNotFound);
1325 };
1326
1327 self.fee_history_provider
1328 .fee_history(block_count, latest_block.block_number(), reward_percentiles)
1329 .await
1330 }
1331
1332 pub fn is_automine(&self) -> bool {
1334 self.automine
1335 }
1336
1337 pub async fn get_automine(&self) -> bool {
1339 get_automine(&self.rpc_client).await
1340 }
1341
1342 pub fn get_block_subscription_rx(&self) -> tokio::sync::broadcast::Receiver<BlockV1> {
1344 self.block_subscription_tx.subscribe()
1345 }
1346
1347 pub fn get_log_subscription_rx(&self) -> tokio::sync::broadcast::Receiver<Log> {
1349 self.log_subscription_tx.subscribe()
1350 }
1351}
1352
1353fn to_hex(bytes: impl AsRef<[u8]>) -> String {
1354 format!("0x{}", hex::encode(bytes.as_ref()))
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359 use super::*;
1360 use sc_rpc_api::state::error::{
1361 CALL_RECORDED_DENIED_ERROR_CODE, CALL_RECORDED_UNSUPPORTED_ERROR_CODE,
1362 };
1363 use subxt::rpcs::UserError;
1364
1365 fn rpc_user_error(code: i32) -> ClientError {
1366 ClientError::RpcError(subxt::rpcs::Error::User(UserError {
1367 code,
1368 message: "..".to_string(),
1369 data: None,
1370 }))
1371 }
1372
1373 #[test]
1374 fn missing_method_is_version_skew() {
1375 assert_eq!(
1376 rpc_user_error(-32601).recorded_unavailable_reason(),
1377 Some(RecordedUnavailable::MethodMissing),
1378 );
1379 }
1380
1381 #[test]
1382 fn unsafe_denied_is_a_fallback_reason() {
1383 assert_eq!(
1384 rpc_user_error(CALL_RECORDED_DENIED_ERROR_CODE).recorded_unavailable_reason(),
1385 Some(RecordedUnavailable::Denied),
1386 );
1387 }
1388
1389 #[test]
1390 fn no_recorder_code_is_expected() {
1391 assert_eq!(
1392 rpc_user_error(CALL_RECORDED_UNSUPPORTED_ERROR_CODE).recorded_unavailable_reason(),
1393 Some(RecordedUnavailable::NoRecorder),
1394 );
1395 }
1396
1397 #[test]
1398 fn genuine_errors_do_not_trigger_fallback() {
1399 assert!(rpc_user_error(-32000).recorded_unavailable_reason().is_none());
1400 assert!(rpc_user_error(-32602).recorded_unavailable_reason().is_none());
1401 assert!(ClientError::EthExtrinsicNotFound.recorded_unavailable_reason().is_none());
1402 assert!(ClientError::BlockNotFound.recorded_unavailable_reason().is_none());
1403 }
1404}