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