Split RPCs into a separate crate (#1910)

* WIP extract RPCs into separate crate

* fmt

* Fix test

* Remove unused deps

* fix import

* WIP: Fix up errors and most tests. Start extracintg some tests/code to rpc crate

* MockRpcClient sync or async

* MockRpcClient only async but better type inference

* WIP MockRpcClient FnMuts and some test updates to use it

* Get all but one test working with new MockRpcClient

* WIP trying to debug failure

* WIP, Tests mostly fixed, need to add back oen more

* Get mock RPC tests working

* fmt

* fmt

* Clippy and comment tweak

* update CI to explicitly check subxt-rpc features

* clippy

* small tweaks after pass over

* feature flag rename

* update some docs

* Fix some examples

* fmt

* Fix features flags to work with web/wasm32

* Fix unused dep warning

* explicit targets in wasm CI

* Add better crate level docs

* fmt

* Address review comments

* Comment out flaky test for now and make more obvious how similar POlkadot and Substrate configs are

* Not a doc comment

* Remove unused imports
This commit is contained in:
James Wilson
2025-02-18 12:07:00 +00:00
committed by GitHub
parent 333de953ec
commit 816a86423b
50 changed files with 4575 additions and 1186 deletions
@@ -2,13 +2,13 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::rpc_methods::{ChainHeadRpcMethods, FollowEvent};
use crate::config::Config;
use crate::error::Error;
use futures::{FutureExt, Stream, StreamExt};
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use subxt_rpcs::methods::chain_head::{ChainHeadRpcMethods, FollowEvent};
/// A `Stream` whose goal is to remain subscribed to `chainHead_follow`. It will re-subscribe if the subscription
/// is ended for any reason, and it will return the current `subscription_id` as an event, along with the other
@@ -113,8 +113,10 @@ impl<Hash> FollowStream<Hash> {
.to_owned(),
));
};
// Return both:
// Map stream errors into the higher level subxt one:
let stream = stream.map_err(|e| e.into());
let stream: FollowEventStream<T::Hash> = Box::pin(stream);
// Return both:
Ok((stream, sub_id))
})
}),
@@ -215,12 +217,10 @@ impl<Hash> Stream for FollowStream<Hash> {
#[cfg(test)]
pub(super) mod test_utils {
use super::*;
use crate::backend::chain_head::rpc_methods::{
BestBlockChanged, Finalized, Initialized, NewBlock,
};
use crate::config::substrate::H256;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use subxt_rpcs::methods::chain_head::{BestBlockChanged, Finalized, Initialized, NewBlock};
/// Given some events, returns a follow stream getter that we can use in
/// place of the usual RPC method.
@@ -3,7 +3,6 @@
// see LICENSE for license details.
use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin};
use crate::backend::chain_head::rpc_methods::{FollowEvent, Initialized, RuntimeEvent};
use crate::config::BlockHash;
use crate::error::{Error, RpcError};
use futures::stream::{Stream, StreamExt};
@@ -12,6 +11,7 @@ use std::ops::DerefMut;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use subxt_rpcs::methods::chain_head::{FollowEvent, Initialized, RuntimeEvent};
/// A `Stream` which builds on `FollowStreamDriver`, and allows multiple subscribers to obtain events
/// from the single underlying subscription (each being provided an `Initialized` message and all new
@@ -454,8 +454,11 @@ where
.iter()
.position(|b| b.hash() == p.hash())
else {
return Poll::Ready(Some(Err(RpcError::DisconnectedWillReconnect(
"Missed at least one block when the connection was lost".to_owned(),
return Poll::Ready(Some(Err(RpcError::ClientError(
subxt_rpcs::Error::DisconnectedWillReconnect(
"Missed at least one block when the connection was lost"
.to_owned(),
),
)
.into())));
};
@@ -739,7 +742,7 @@ mod test {
)
);
assert!(
matches!(&evs[1], Err(Error::Rpc(RpcError::DisconnectedWillReconnect(e))) if e.contains("Missed at least one block when the connection was lost"))
matches!(&evs[1], Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(e)))) if e.contains("Missed at least one block when the connection was lost"))
);
assert_eq!(
evs[2].as_ref().unwrap(),
@@ -4,12 +4,12 @@
use super::follow_stream::FollowStream;
use super::ChainHeadRpcMethods;
use crate::backend::chain_head::rpc_methods::{
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock,
};
use crate::config::{BlockHash, Config};
use crate::error::Error;
use futures::stream::{FuturesUnordered, Stream, StreamExt};
use subxt_rpcs::methods::chain_head::{
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock,
};
use std::collections::{HashMap, HashSet};
use std::future::Future;
+32 -28
View File
@@ -16,15 +16,10 @@ mod follow_stream_driver;
mod follow_stream_unpin;
mod storage_items;
pub mod rpc_methods;
use self::follow_stream_driver::FollowStreamFinalizedHeads;
use self::rpc_methods::{
FollowEvent, MethodResponse, RuntimeEvent, StorageQuery, StorageQueryType, StorageResultType,
};
use crate::backend::{
rpc::RpcClient, utils::retry, Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse,
StreamOf, StreamOfResults, TransactionStatus,
utils::retry, Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse, StreamOf,
StreamOfResults, TransactionStatus,
};
use crate::config::BlockHash;
use crate::error::{Error, RpcError};
@@ -36,9 +31,18 @@ use futures::{Stream, StreamExt};
use std::collections::HashMap;
use std::task::Poll;
use storage_items::StorageItems;
use subxt_rpcs::methods::chain_head::{
FollowEvent, MethodResponse, RuntimeEvent, StorageQuery, StorageQueryType, StorageResultType,
};
use subxt_rpcs::RpcClient;
/// Re-export RPC types and methods from [`subxt_rpcs::methods::chain_head`].
pub mod rpc_methods {
pub use subxt_rpcs::methods::legacy::*;
}
// Expose the RPC methods.
pub use rpc_methods::ChainHeadRpcMethods;
pub use subxt_rpcs::methods::chain_head::ChainHeadRpcMethods;
/// Configure and build an [`ChainHeadBackend`].
pub struct ChainHeadBackendBuilder<T> {
@@ -213,7 +217,7 @@ impl<T: Config> ChainHeadBackend<T> {
let header = match res {
Ok(header) => header,
Err(e) => return Some(Err(e)),
Err(e) => return Some(Err(e.into())),
};
Some(Ok((header, block_ref.into())))
@@ -338,13 +342,18 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
}
async fn genesis_hash(&self) -> Result<T::Hash, Error> {
retry(|| self.methods.chainspec_v1_genesis_hash()).await
retry(|| async {
let genesis_hash = self.methods.chainspec_v1_genesis_hash().await?;
Ok(genesis_hash)
})
.await
}
async fn block_header(&self, at: T::Hash) -> Result<Option<T::Header>, Error> {
retry(|| async {
let sub_id = get_subscription_id(&self.follow_handle).await?;
self.methods.chainhead_v1_header(&sub_id, at).await
let header = self.methods.chainhead_v1_header(&sub_id, at).await?;
Ok(header)
})
.await
}
@@ -357,9 +366,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
let follow_events = self.follow_handle.subscribe().events();
let status = self.methods.chainhead_v1_body(&sub_id, at).await?;
let operation_id = match status {
MethodResponse::LimitReached => {
return Err(RpcError::request_rejected("limit reached").into())
}
MethodResponse::LimitReached => return Err(RpcError::LimitReached.into()),
MethodResponse::Started(s) => s.operation_id,
};
@@ -653,22 +660,21 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
let tx_progress_ev = match tx_progress.poll_next_unpin(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(err_other("No more transaction progress events, but we haven't seen a Finalized one yet")),
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Some(Ok(ev))) => ev,
};
// When we get one, map it to the correct format (or for finalized ev, wait for the pinned block):
use subxt_rpcs::methods::chain_head::TransactionStatus as RpcTransactionStatus;
let tx_progress_ev = match tx_progress_ev {
rpc_methods::TransactionStatus::Finalized { block } => {
RpcTransactionStatus::Finalized { block } => {
// We'll wait until we have seen this hash, to try to guarantee
// that when we return this event, the corresponding block is
// pinned and accessible.
finalized_hash = Some(block.hash);
continue;
}
rpc_methods::TransactionStatus::BestChainBlockIncluded {
block: Some(block),
} => {
RpcTransactionStatus::BestChainBlockIncluded { block: Some(block) } => {
// Look up a pinned block ref if we can, else return a non-pinned
// block that likely isn't accessible. We have no guarantee that a best
// block on the node a tx was sent to will ever be known about on the
@@ -679,20 +685,20 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
};
TransactionStatus::InBestBlock { hash: block_ref }
}
rpc_methods::TransactionStatus::BestChainBlockIncluded { block: None } => {
RpcTransactionStatus::BestChainBlockIncluded { block: None } => {
TransactionStatus::NoLongerInBestBlock
}
rpc_methods::TransactionStatus::Broadcasted => TransactionStatus::Broadcasted,
rpc_methods::TransactionStatus::Dropped { error, .. } => {
RpcTransactionStatus::Broadcasted => TransactionStatus::Broadcasted,
RpcTransactionStatus::Dropped { error, .. } => {
TransactionStatus::Dropped { message: error }
}
rpc_methods::TransactionStatus::Error { error } => {
RpcTransactionStatus::Error { error } => {
TransactionStatus::Error { message: error }
}
rpc_methods::TransactionStatus::Invalid { error } => {
RpcTransactionStatus::Invalid { error } => {
TransactionStatus::Invalid { message: error }
}
rpc_methods::TransactionStatus::Validated => TransactionStatus::Validated,
RpcTransactionStatus::Validated => TransactionStatus::Validated,
};
return Poll::Ready(Some(Ok(tx_progress_ev)));
}
@@ -718,9 +724,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
.chainhead_v1_call(&sub_id, at, method, call_parameters)
.await?;
let operation_id = match status {
MethodResponse::LimitReached => {
return Err(RpcError::request_rejected("limit reached").into())
}
MethodResponse::LimitReached => return Err(RpcError::LimitReached.into()),
MethodResponse::Started(s) => s.operation_id,
};
File diff suppressed because it is too large Load Diff
+10 -7
View File
@@ -4,9 +4,6 @@
use super::follow_stream_driver::FollowStreamDriverHandle;
use super::follow_stream_unpin::BlockRef;
use super::rpc_methods::{
ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult,
};
use crate::config::Config;
use crate::error::{Error, RpcError};
use futures::{FutureExt, Stream, StreamExt};
@@ -15,6 +12,9 @@ use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use subxt_rpcs::methods::chain_head::{
ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult,
};
/// Obtain a stream of storage items given some query. this handles continuing
/// and stopping under the hood, and returns a stream of `StorageResult`s.
@@ -45,9 +45,7 @@ impl<T: Config> StorageItems<T> {
.chainhead_v1_storage(&sub_id, at, queries, None)
.await?;
let operation_id: Arc<str> = match status {
MethodResponse::LimitReached => {
return Err(RpcError::request_rejected("limit reached").into())
}
MethodResponse::LimitReached => return Err(RpcError::LimitReached.into()),
MethodResponse::Started(s) => s.operation_id.into(),
};
@@ -59,7 +57,12 @@ impl<T: Config> StorageItems<T> {
let operation_id = operation_id.clone();
let methods = methods.clone();
Box::pin(async move { methods.chainhead_v1_continue(&sub_id, &operation_id).await })
Box::pin(async move {
methods
.chainhead_v1_continue(&sub_id, &operation_id)
.await?;
Ok(())
})
})
};