rpc: stabilize chainhead backend (#1802)

* rpc: stabilize ChainHeadBackend

* remove noise from example

* add missing features

* make tests compile

* make tests compile v2

* revert stop event

* feature-gate runtime

* Update subxt/Cargo.toml

* add docsrs feature stuff

* Update subxt/src/backend/chain_head/mod.rs

* Update subxt/src/backend/chain_head/mod.rs

* Update subxt/src/backend/chain_head/mod.rs
This commit is contained in:
Niklas Adolfsson
2024-10-03 18:14:38 +02:00
committed by GitHub
parent 7f9a1a17c0
commit 3807b29f36
22 changed files with 302 additions and 184 deletions
@@ -2,7 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::rpc_methods::{FollowEvent, UnstableRpcMethods};
use super::rpc_methods::{ChainHeadRpcMethods, FollowEvent};
use crate::config::Config;
use crate::error::Error;
use futures::{FutureExt, Stream, StreamExt};
@@ -99,7 +99,7 @@ impl<Hash> FollowStream<Hash> {
}
/// Create a new [`FollowStream`] given the RPC methods.
pub fn from_methods<T: Config>(methods: UnstableRpcMethods<T>) -> FollowStream<T::Hash> {
pub fn from_methods<T: Config>(methods: ChainHeadRpcMethods<T>) -> FollowStream<T::Hash> {
FollowStream {
stream_getter: Box::new(move || {
let methods = methods.clone();
@@ -215,7 +215,7 @@ impl<Hash> Stream for FollowStream<Hash> {
#[cfg(test)]
pub(super) mod test_utils {
use super::*;
use crate::backend::unstable::rpc_methods::{
use crate::backend::chain_head::rpc_methods::{
BestBlockChanged, Finalized, Initialized, NewBlock,
};
use crate::config::substrate::H256;
@@ -3,7 +3,7 @@
// see LICENSE for license details.
use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin};
use crate::backend::unstable::rpc_methods::{FollowEvent, Initialized, RuntimeEvent};
use crate::backend::chain_head::rpc_methods::{FollowEvent, Initialized, RuntimeEvent};
use crate::config::BlockHash;
use crate::error::{Error, RpcError};
use futures::stream::{Stream, StreamExt};
@@ -3,8 +3,8 @@
// see LICENSE for license details.
use super::follow_stream::FollowStream;
use super::UnstableRpcMethods;
use crate::backend::unstable::rpc_methods::{
use super::ChainHeadRpcMethods;
use crate::backend::chain_head::rpc_methods::{
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock,
};
use crate::config::{BlockHash, Config};
@@ -275,7 +275,7 @@ impl<Hash: BlockHash> FollowStreamUnpin<Hash> {
/// Create a new [`FollowStreamUnpin`] given the RPC methods.
pub fn from_methods<T: Config>(
follow_stream: FollowStream<T::Hash>,
methods: UnstableRpcMethods<T>,
methods: ChainHeadRpcMethods<T>,
max_block_life: usize,
) -> FollowStreamUnpin<T::Hash> {
let unpin_method = Box::new(move |hash: T::Hash, sub_id: Arc<str>| {
@@ -38,22 +38,22 @@ use std::task::Poll;
use storage_items::StorageItems;
// Expose the RPC methods.
pub use rpc_methods::UnstableRpcMethods;
pub use rpc_methods::ChainHeadRpcMethods;
/// Configure and build an [`UnstableBackend`].
pub struct UnstableBackendBuilder<T> {
/// Configure and build an [`ChainHeadBackend`].
pub struct ChainHeadBackendBuilder<T> {
max_block_life: usize,
_marker: std::marker::PhantomData<T>,
}
impl<T: Config> Default for UnstableBackendBuilder<T> {
impl<T: Config> Default for ChainHeadBackendBuilder<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Config> UnstableBackendBuilder<T> {
/// Create a new [`UnstableBackendBuilder`].
impl<T: Config> ChainHeadBackendBuilder<T> {
/// Create a new [`ChainHeadBackendBuilder`].
pub fn new() -> Self {
Self {
max_block_life: usize::MAX,
@@ -73,15 +73,20 @@ impl<T: Config> UnstableBackendBuilder<T> {
self
}
/// Given an [`RpcClient`] to use to make requests, this returns a tuple of an [`UnstableBackend`],
/// which implements the [`Backend`] trait, and an [`UnstableBackendDriver`] which must be polled in
/// order for the backend to make progress.
/// 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<RpcClient>,
) -> (UnstableBackend<T>, UnstableBackendDriver<T>) {
) -> (ChainHeadBackend<T>, ChainHeadBackendDriver<T>) {
// Construct the underlying follow_stream layers:
let rpc_methods = UnstableRpcMethods::new(client.into());
let rpc_methods = ChainHeadRpcMethods::new(client.into());
let follow_stream =
follow_stream::FollowStream::<T::Hash>::from_methods(rpc_methods.clone());
let follow_stream_unpin = follow_stream_unpin::FollowStreamUnpin::<T::Hash>::from_methods(
@@ -92,26 +97,61 @@ impl<T: Config> UnstableBackendBuilder<T> {
let follow_stream_driver = FollowStreamDriver::new(follow_stream_unpin);
// Wrap these into the backend and driver that we'll expose.
let backend = UnstableBackend {
let backend = ChainHeadBackend {
methods: rpc_methods,
follow_handle: follow_stream_driver.handle(),
};
let driver = UnstableBackendDriver {
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<RpcClient>) -> ChainHeadBackend<T> {
fn spawn<F: std::future::Future + Send + 'static>(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 {
while let Some(res) = driver.next().await {
if let Err(e) = res {
if !e.is_disconnected_will_reconnect() {
tracing::debug!(target: "subxt", "chainHead driver was closed: {e}");
break;
}
}
}
});
backend
}
}
/// Driver for the [`UnstableBackend`]. This must be polled in order for the
/// Driver for the [`ChainHeadBackend`]. This must be polled in order for the
/// backend to make progress.
#[derive(Debug)]
pub struct UnstableBackendDriver<T: Config> {
pub struct ChainHeadBackendDriver<T: Config> {
driver: FollowStreamDriver<T::Hash>,
}
impl<T: Config> Stream for UnstableBackendDriver<T> {
impl<T: Config> Stream for ChainHeadBackendDriver<T> {
type Item = <FollowStreamDriver<T::Hash> as Stream>::Item;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
@@ -121,19 +161,19 @@ impl<T: Config> Stream for UnstableBackendDriver<T> {
}
}
/// The unstable backend.
/// The chainHead backend.
#[derive(Debug, Clone)]
pub struct UnstableBackend<T: Config> {
pub struct ChainHeadBackend<T: Config> {
// RPC methods we'll want to call:
methods: UnstableRpcMethods<T>,
methods: ChainHeadRpcMethods<T>,
// A handle to the chainHead_follow subscription:
follow_handle: FollowStreamDriverHandle<T::Hash>,
}
impl<T: Config> UnstableBackend<T> {
/// Configure and construct an [`UnstableBackend`] and the associated [`UnstableBackendDriver`].
pub fn builder() -> UnstableBackendBuilder<T> {
UnstableBackendBuilder::new()
impl<T: Config> ChainHeadBackend<T> {
/// Configure and construct an [`ChainHeadBackend`] and the associated [`ChainHeadBackendDriver`].
pub fn builder() -> ChainHeadBackendBuilder<T> {
ChainHeadBackendBuilder::new()
}
/// Stream block headers based on the provided filter fn
@@ -193,10 +233,10 @@ impl<Hash: BlockHash + 'static> From<follow_stream_unpin::BlockRef<Hash>> for Bl
}
}
impl<T: Config> super::sealed::Sealed for UnstableBackend<T> {}
impl<T: Config> super::sealed::Sealed for ChainHeadBackend<T> {}
#[async_trait]
impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
async fn storage_fetch_values(
&self,
keys: Vec<Vec<u8>>,
@@ -19,15 +19,15 @@ use std::task::Poll;
/// some `T: Config` trait which determines some of the types that the RPC methods will
/// take or hand back.
#[derive_where(Clone, Debug)]
pub struct UnstableRpcMethods<T> {
pub struct ChainHeadRpcMethods<T> {
client: RpcClient,
_marker: std::marker::PhantomData<T>,
}
impl<T: Config> UnstableRpcMethods<T> {
impl<T: Config> ChainHeadRpcMethods<T> {
/// Instantiate the legacy RPC method interface.
pub fn new(client: RpcClient) -> Self {
UnstableRpcMethods {
ChainHeadRpcMethods {
client,
_marker: std::marker::PhantomData,
}
@@ -36,15 +36,15 @@ impl<T: Config> UnstableRpcMethods<T> {
/// Subscribe to `chainHead_v1_follow` to obtain all reported blocks by the chain.
///
/// The subscription ID can be used to make queries for the
/// block's body ([`chainHead_v1_body`](UnstableRpcMethods::chainhead_v1_follow)),
/// block's header ([`chainHead_v1_header`](UnstableRpcMethods::chainhead_v1_header)),
/// block's storage ([`chainHead_v1_storage`](UnstableRpcMethods::chainhead_v1_storage)) and submitting
/// runtime API calls at this block ([`chainHead_v1_call`](UnstableRpcMethods::chainhead_v1_call)).
/// block's body ([`chainHead_v1_body`](ChainHeadRpcMethods::chainhead_v1_follow)),
/// block's header ([`chainHead_v1_header`](ChainHeadRpcMethods::chainhead_v1_header)),
/// block's storage ([`chainHead_v1_storage`](ChainHeadRpcMethods::chainhead_v1_storage)) and submitting
/// runtime API calls at this block ([`chainHead_v1_call`](ChainHeadRpcMethods::chainhead_v1_call)).
///
/// # Note
///
/// When the user is no longer interested in a block, the user is responsible
/// for calling the [`chainHead_v1_unpin`](UnstableRpcMethods::chainhead_v1_unpin) method.
/// for calling the [`chainHead_v1_unpin`](ChainHeadRpcMethods::chainhead_v1_unpin) method.
/// Failure to do so will result in the subscription being stopped by generating the `Stop` event.
pub async fn chainhead_v1_follow(
&self,
@@ -106,7 +106,7 @@ impl<T: Config> UnstableRpcMethods<T> {
/// # Note
///
/// The subscription ID is obtained from an open subscription created by
/// [`chainHead_v1_follow`](UnstableRpcMethods::chainhead_v1_follow).
/// [`chainHead_v1_follow`](ChainHeadRpcMethods::chainhead_v1_follow).
pub async fn chainhead_v1_body(
&self,
subscription_id: &str,
@@ -125,7 +125,7 @@ impl<T: Config> UnstableRpcMethods<T> {
/// # Note
///
/// The subscription ID is obtained from an open subscription created by
/// [`chainHead_v1_follow`](UnstableRpcMethods::chainhead_v1_follow).
/// [`chainHead_v1_follow`](ChainHeadRpcMethods::chainhead_v1_follow).
pub async fn chainhead_v1_header(
&self,
subscription_id: &str,
@@ -151,7 +151,7 @@ impl<T: Config> UnstableRpcMethods<T> {
/// # Note
///
/// The subscription ID is obtained from an open subscription created by
/// [`chainHead_v1_follow`](UnstableRpcMethods::chainhead_v1_follow).
/// [`chainHead_v1_follow`](ChainHeadRpcMethods::chainhead_v1_follow).
pub async fn chainhead_v1_storage(
&self,
subscription_id: &str,
@@ -186,7 +186,7 @@ impl<T: Config> UnstableRpcMethods<T> {
/// # Note
///
/// The subscription ID is obtained from an open subscription created by
/// [`chainHead_v1_follow`](UnstableRpcMethods::chainhead_v1_follow).
/// [`chainHead_v1_follow`](ChainHeadRpcMethods::chainhead_v1_follow).
pub async fn chainhead_v1_call(
&self,
subscription_id: &str,
@@ -210,7 +210,7 @@ impl<T: Config> UnstableRpcMethods<T> {
/// # Note
///
/// The subscription ID is obtained from an open subscription created by
/// [`chainHead_v1_follow`](UnstableRpcMethods::chainhead_v1_follow).
/// [`chainHead_v1_follow`](ChainHeadRpcMethods::chainhead_v1_follow).
pub async fn chainhead_v1_unpin(
&self,
subscription_id: &str,
@@ -5,7 +5,7 @@
use super::follow_stream_driver::FollowStreamDriverHandle;
use super::follow_stream_unpin::BlockRef;
use super::rpc_methods::{
FollowEvent, MethodResponse, StorageQuery, StorageResult, UnstableRpcMethods,
ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult,
};
use crate::config::Config;
use crate::error::{Error, RpcError};
@@ -35,7 +35,7 @@ impl<T: Config> StorageItems<T> {
queries: impl Iterator<Item = StorageQuery<&[u8]>>,
at: T::Hash,
follow_handle: &FollowStreamDriverHandle<T::Hash>,
methods: UnstableRpcMethods<T>,
methods: ChainHeadRpcMethods<T>,
) -> Result<Self, Error> {
let sub_id = super::get_subscription_id(follow_handle).await?;
+14 -11
View File
@@ -6,9 +6,9 @@
//! the necessary information (probably from a JSON-RPC API, but that's up to the
//! implementation).
pub mod chain_head;
pub mod legacy;
pub mod rpc;
pub mod unstable;
pub mod utils;
use subxt_core::client::RuntimeVersion;
@@ -881,18 +881,18 @@ mod test {
OperationStorageItems, RuntimeSpec, RuntimeVersionEvent,
};
use super::unstable::*;
use super::chain_head::*;
use super::*;
fn build_backend(
rpc_client: impl RpcClientT,
) -> (UnstableBackend<Conf>, UnstableBackendDriver<Conf>) {
let (backend, driver): (UnstableBackend<Conf>, _) =
UnstableBackend::builder().build(rpc_client);
) -> (ChainHeadBackend<Conf>, ChainHeadBackendDriver<Conf>) {
let (backend, driver): (ChainHeadBackend<Conf>, _) =
ChainHeadBackend::builder().build(rpc_client);
(backend, driver)
}
fn build_backend_spawn_background(rpc_client: impl RpcClientT) -> UnstableBackend<Conf> {
fn build_backend_spawn_background(rpc_client: impl RpcClientT) -> ChainHeadBackend<Conf> {
let (backend, mut driver) = build_backend(rpc_client);
tokio::spawn(async move {
while let Some(val) = driver.next().await {
@@ -931,7 +931,7 @@ mod test {
serde_json::from_value(spec).unwrap()
}
type FollowEvent = unstable::rpc_methods::FollowEvent<<Conf as Config>::Hash>;
type FollowEvent = chain_head::rpc_methods::FollowEvent<<Conf as Config>::Hash>;
fn setup_mock_rpc_client(cycle_ids: bool) -> MockRpcBuilder {
let hash = random_hash();
@@ -993,13 +993,16 @@ mod test {
operation_id: id.to_owned(),
})
}
fn storage_result(key: &str, value: &str) -> unstable::rpc_methods::StorageResult {
unstable::rpc_methods::StorageResult {
fn storage_result(key: &str, value: &str) -> chain_head::rpc_methods::StorageResult {
chain_head::rpc_methods::StorageResult {
key: Bytes(key.to_owned().into()),
result: rpc_methods::StorageResultType::Value(Bytes(value.to_owned().into())),
}
}
fn storage_items(id: &str, items: &[unstable::rpc_methods::StorageResult]) -> FollowEvent {
fn storage_items(
id: &str,
items: &[chain_head::rpc_methods::StorageResult],
) -> FollowEvent {
FollowEvent::OperationStorageItems(OperationStorageItems {
operation_id: id.to_owned(),
items: VecDeque::from(items.to_owned()),
@@ -1400,7 +1403,7 @@ mod test {
.add_mock_data(mock_data)
.add_mock_data(response_data)
.build();
let (backend, mut driver): (UnstableBackend<Conf>, _) = build_backend(rpc_client);
let (backend, mut driver): (ChainHeadBackend<Conf>, _) = build_backend(rpc_client);
let _ = driver.next().await.unwrap();
let _ = driver.next().await.unwrap();