// 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 will expose a backend implementation based on the new APIs
//! described at . See
//! [`rpc_methods`] for the raw API calls.
//!
//! # Warning
//!
//! Everything in this module is **unstable**, meaning that it could change without
//! warning at any time.
mod follow_stream;
mod follow_stream_driver;
mod follow_stream_unpin;
mod storage_items;
use self::follow_stream_driver::FollowStreamFinalizedHeads;
use crate::{
backend::{
Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults,
TransactionStatus, utils::retry,
},
config::{Config, Hash, HashFor},
error::{BackendError, RpcError},
};
use async_trait::async_trait;
use follow_stream_driver::{FollowStreamDriver, FollowStreamDriverHandle};
use futures::{Stream, StreamExt, future::Either};
use pezkuwi_subxt_rpcs::{
RpcClient,
methods::chain_head::{
FollowEvent, MethodResponse, RuntimeEvent, StorageQuery, StorageQueryType,
StorageResultType,
},
};
use std::{collections::HashMap, task::Poll};
use storage_items::StorageItems;
/// Re-export RPC types and methods from [`pezkuwi_subxt_rpcs::methods::chain_head`].
pub mod rpc_methods {
pub use pezkuwi_subxt_rpcs::methods::legacy::*;
}
// Expose the RPC methods.
pub use pezkuwi_subxt_rpcs::methods::chain_head::ChainHeadRpcMethods;
/// Configure and build an [`ChainHeadBackend`].
pub struct ChainHeadBackendBuilder {
max_block_life: usize,
transaction_timeout_secs: usize,
submit_transactions_ignoring_follow_events: bool,
_marker: std::marker::PhantomData,
}
impl Default for ChainHeadBackendBuilder {
fn default() -> Self {
Self::new()
}
}
impl ChainHeadBackendBuilder {
/// Create a new [`ChainHeadBackendBuilder`].
pub fn new() -> Self {
Self {
max_block_life: usize::MAX,
transaction_timeout_secs: 240,
submit_transactions_ignoring_follow_events: false,
_marker: std::marker::PhantomData,
}
}
/// The age of a block is defined here as the difference between the current finalized block
/// number and the block number of a given block. Once the difference equals or exceeds the
/// number given here, the block is unpinned.
///
/// By default, we will never automatically unpin blocks, but if the number of pinned blocks
/// that we keep hold of exceeds the number that the server can tolerate, then a `stop` event
/// is generated and we are forced to resubscribe, losing any pinned blocks.
pub fn max_block_life(mut self, max_block_life: usize) -> Self {
self.max_block_life = max_block_life;
self
}
/// When a transaction is submitted, we wait for events indicating it's successfully made it
/// into a finalized block. If it takes too long for this to happen, we assume that something
/// went wrong and that we should give up waiting.
///
/// Provide a value here to denote how long, in seconds, to wait before giving up. Defaults to
/// 240 seconds.
///
/// If [`Self::submit_transactions_ignoring_follow_events()`] is called, this timeout is
/// ignored.
pub fn transaction_timeout(mut self, timeout_secs: usize) -> Self {
self.transaction_timeout_secs = timeout_secs;
self
}
/// When a transaction is submitted, we normally synchronize the events that we get back with
/// events from our background `chainHead_follow` subscription, to ensure that any blocks
/// hashes that we see can be immediately queried (for example to get events or state at that
/// block), and are kept around unless they are no longer needed.
///
/// The main downside of this synchronization is that there may be a delay in being handed back
/// a [`TransactionStatus::InFinalizedBlock`] event while we wait to see the same block hash
/// emitted from our background `chainHead_follow` subscription in order to ensure it's
/// available for querying.
///
/// Calling this method turns off this synchronization, speeding up the response and removing
/// any reliance on the `chainHead_follow` subscription continuing to run without stopping
/// throughout submitting a transaction.
///
/// # Warning
///
/// This can lead to errors when calling APIs like `wait_for_finalized_success`, which will try
/// to retrieve events at the finalized block, because there will be a race and the finalized
/// block may not be available for querying yet.
pub fn submit_transactions_ignoring_follow_events(mut self) -> Self {
self.submit_transactions_ignoring_follow_events = true;
self
}
/// A low-level API to build the backend and driver which requires polling the driver for the
/// backend to make progress.
///
/// This is useful if you want to manage the driver yourself, for example if you want to run it
/// in on a specific runtime.
///
/// If you just want to run the driver in the background until completion in on the default
/// runtime, use [`ChainHeadBackendBuilder::build_with_background_driver`] instead.
pub fn build(
self,
client: impl Into,
) -> (ChainHeadBackend, ChainHeadBackendDriver) {
// Construct the underlying follow_stream layers:
let rpc_methods = ChainHeadRpcMethods::new(client.into());
let follow_stream =
follow_stream::FollowStream::>::from_methods(rpc_methods.clone());
let follow_stream_unpin =
follow_stream_unpin::FollowStreamUnpin::>::from_methods(
follow_stream,
rpc_methods.clone(),
self.max_block_life,
);
let follow_stream_driver = FollowStreamDriver::new(follow_stream_unpin);
// Wrap these into the backend and driver that we'll expose.
let backend = ChainHeadBackend {
methods: rpc_methods,
follow_handle: follow_stream_driver.handle(),
transaction_timeout_secs: self.transaction_timeout_secs,
submit_transactions_ignoring_follow_events: self
.submit_transactions_ignoring_follow_events,
};
let driver = ChainHeadBackendDriver { driver: follow_stream_driver };
(backend, driver)
}
/// An API to build the backend and driver which will run in the background until completion
/// on the default runtime.
///
/// - On non-wasm targets, this will spawn the driver on `tokio`.
/// - On wasm targets, this will spawn the driver on `wasm-bindgen-futures`.
#[cfg(feature = "runtime")]
#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
pub fn build_with_background_driver(self, client: impl Into) -> ChainHeadBackend {
fn spawn(future: F) {
#[cfg(not(target_family = "wasm"))]
tokio::spawn(async move {
future.await;
});
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
wasm_bindgen_futures::spawn_local(async move {
future.await;
});
}
let (backend, mut driver) = self.build(client);
spawn(async move {
// NOTE: we need to poll the driver until it's done i.e returns None
// to ensure that the backend is shutdown properly.
while let Some(res) = driver.next().await {
if let Err(err) = res {
tracing::debug!(target: "subxt", "chainHead backend error={err}");
}
}
tracing::debug!(target: "subxt", "chainHead backend was closed");
});
backend
}
}
/// Driver for the [`ChainHeadBackend`]. This must be polled in order for the
/// backend to make progress.
#[derive(Debug)]
pub struct ChainHeadBackendDriver {
driver: FollowStreamDriver>,
}
impl Stream for ChainHeadBackendDriver {
type Item = > as Stream>::Item;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll