// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. //! This module exposes a backend implementation based on the new APIs //! described at . See //! [`rpc_methods`] for the raw API calls. //! //! Specifically, the focus here is on the `archive` methods. These can only be used //! to interact with archive nodes, but are less restrictive than the `chainHead` methods //! in terms of the allowed operations. mod storage_stream; use crate::backend::{ Backend, BlockRef, StorageResponse, StreamOf, StreamOfResults, TransactionStatus, utils::retry, }; use crate::config::{Config, HashFor, RpcConfigFor}; use crate::error::BackendError; use async_trait::async_trait; use futures::StreamExt; use subxt_rpcs::RpcClient; use subxt_rpcs::methods::chain_head::{ ArchiveStorageQuery, ArchiveCallResult, StorageQueryType, }; use storage_stream::ArchiveStorageStream; // Expose the RPC methods. pub use subxt_rpcs::methods::chain_head::ChainHeadRpcMethods as ArchiveRpcMethods; /// The archive backend. #[derive(Debug, Clone)] pub struct ArchiveBackend { // RPC methods we'll want to call: methods: ArchiveRpcMethods>, } impl ArchiveBackend { /// Configure and construct an [`ArchiveBackend`] and the associated [`ChainHeadBackendDriver`]. pub fn new(client: impl Into,) -> ArchiveBackend { let methods = ArchiveRpcMethods::new(client.into()); ArchiveBackend { methods } } } #[async_trait] impl Backend for ArchiveBackend { async fn storage_fetch_values( &self, keys: Vec>, at: HashFor, ) -> Result, BackendError> { let queries = keys.into_iter() .map(|key| ArchiveStorageQuery { key: key, query_type: StorageQueryType::Value, pagination_start_key: None, }) .collect(); let stream = ArchiveStorageStream::new(at, self.methods.clone(), queries).map(|item| { match item { Err(e) => Some(Err(e)), Ok(item) => item.value.map(|val| Ok(StorageResponse { key: item.key.0, value: val.0 })) } }).filter_map(async |item| item); Ok(StreamOf(Box::pin(stream))) } async fn storage_fetch_descendant_keys( &self, key: Vec, at: HashFor, ) -> Result>, BackendError> { let queries = std::iter::once(ArchiveStorageQuery { key: key, // Just ask for the hash and then ignore it and return keys query_type: StorageQueryType::DescendantsHashes, pagination_start_key: None, }) .collect(); let stream = ArchiveStorageStream::new(at, self.methods.clone(), queries).map(|item| { match item { Err(e) => Err(e), Ok(item) => Ok(item.key.0) } }); Ok(StreamOf(Box::pin(stream))) } async fn storage_fetch_descendant_values( &self, key: Vec, at: HashFor, ) -> Result, BackendError> { let queries = std::iter::once(ArchiveStorageQuery { key: key, query_type: StorageQueryType::DescendantsValues, pagination_start_key: None, }) .collect(); let stream = ArchiveStorageStream::new(at, self.methods.clone(), queries).map(|item| { match item { Err(e) => Some(Err(e)), Ok(item) => item.value.map(|val| Ok(StorageResponse { key: item.key.0, value: val.0 })) } }).filter_map(async |item| item); Ok(StreamOf(Box::pin(stream))) } async fn genesis_hash(&self) -> Result, BackendError> { retry(|| async { let hash = self.methods.archive_v1_genesis_hash().await?; Ok(hash) }) .await } async fn block_header(&self, at: HashFor) -> Result, BackendError> { retry(|| async { let header = self.methods.archive_v1_header(at).await?; Ok(header) }) .await } async fn block_body(&self, at: HashFor) -> Result>>, BackendError> { retry(|| async { let Some(exts) = self.methods.archive_v1_body(at).await? else { return Ok(None); }; Ok(Some( exts.into_iter().map(|ext| ext.0).collect() )) }) .await } async fn latest_finalized_block_ref(&self) -> Result>, BackendError> { retry(|| async { let height = self.methods.archive_v1_finalized_height().await?; let mut hashes = self.methods.archive_v1_hash_by_height(height).await?; let Some(hash) = hashes.pop() else { return Err(BackendError::Other("Multiple hashes not expected at a finalized height".into())) }; Ok(BlockRef::from_hash(hash)) }) .await } async fn stream_all_block_headers( &self, _hasher: T::Hasher, ) -> Result>)>, BackendError> { Err(BackendError::Other("The archive backend cannot stream block headers".into())) } async fn stream_best_block_headers( &self, _hasher: T::Hasher, ) -> Result>)>, BackendError> { Err(BackendError::Other("The archive backend cannot stream block headers".into())) } async fn stream_finalized_block_headers( &self, _hasher: T::Hasher, ) -> Result>)>, BackendError> { Err(BackendError::Other("The archive backend cannot stream block headers".into())) } async fn submit_transaction( &self, extrinsic: &[u8], ) -> Result>>, BackendError> { // This chainHead impl does not use chainHead_follow and so is suitable here too. super::chain_head::submit_transaction_ignoring_follow_events(extrinsic, &self.methods).await } async fn call( &self, method: &str, call_parameters: Option<&[u8]>, at: HashFor, ) -> Result, BackendError> { let res = self.methods.archive_v1_call(at, method, call_parameters.unwrap_or(&[])).await?; match res { ArchiveCallResult::Success(bytes) => Ok(bytes.0), ArchiveCallResult::Error(e) => Err(BackendError::other(e)), } } } impl crate::backend::sealed::Sealed for ArchiveBackend {}