mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +00:00
Merge v0.50.x to master (#2127)
* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100) * WIP integrating new frame-decode and working out new storage APIS * WIP: first pass adding new storage things to subxt-core * Second pass over Address type and start impl in Subxt * WIP new storage APIs * WIP New storage APIs roughly completed, lots of errors still * Remove PlainorMap enum; plain and map values now use same struct to simplify usage * Begin 'fixing' errors * WIP splitting errors and tidying payload/address traits * Get subxt-core compiling * Small fixes in subxt-core and remove metadata mod * subxt-core: cargo check --all-targets passes * Fix test * WIP starting to update subxt from subxt-core changes * WIP splitting up subxt errors into smaller variants * WIP errors: add DispatchError errors * Port new Storage APIs to subxt-core * cargo check -p subxt passes * Quick-fix errors in subxt-cli (explore subcommand) * fmt * Finish fixing codegen up and start fixing examples * get Subxt examples compiling and bytes_at for constants * Add some arcs to limit lifetimes in subxt/subxt-core storage APIs * A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt * Update codegen test * cargo check --all-targets passing * cargo check --features 'unstable-light-client' passing * clippy * Remove unused dep in subxt * use published frame-decode * fix wasm-example * Add new tx extension to fix daily tests * Remove unused subxt_core::dynamic::DecodedValue type * Update book to match changes * Update docs to fix more broken bits * Add missing docs * fmt * allow larger result errs for now * Add missing alloc imports in subxt-core * Fix doc tests and fix bug getting constant info * Fix V14 -> Metadata transform for storage & constants * Fix parachain example * Fix FFI example * BlockLength decodes t ostruct, not u128 * use fetch/iter shorthands rather than entry in most storage tests * Fix some integration tests * Fix Runtime codegen tests * Expose the dynamic custom_value selecter and use in a UI test * Update codegen metadata * Tidy CLI storage query and support (str,str) as a storage address * Add (str,str) as valid constant address too * Show string tuple in constants example * Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them * clippy * [v0.50] update scale-info-legacy and frame-decode to latest (#2119) * bump scale-info-legacy and frame-decode to latest * Remove something we don't need in this PR * Fully remove unused for now dep * [v0.50] Convert historic metadata to subxt::Metadata (#2120) * First pass converting historic metadatas to our subxt::Metadata type * use published frame-decode * fmt and rename legacy metadata macro * Enable legacy feature where needed in subxt_metadata so it compiles on its own * Use cargo hack more in CI and fix subxt-metadata features * Add tests for metadata conversion (need to optimise; some too expensive right now * Address performance and equality issues in metadata conversion testing * fmt * fmt all * clippy * Fix a doc link * Test codegen and fixes to make it work * Remove local frame-decode patch * bump frame-decode to latest * [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124) * Allow visiting extrinsic fields * fmt * Don't use local scale-decode dep * Clippy and tidy * Extend 'subxt codegen' CLI to work with legacy metadatas * Simplify historic extrinsics example now that AccountId32s have paths/names * clippy * clippy * clippy.. * Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime * Try to fix flaky test * Add custom value decode to extrinsics example * Remove useless else branch ra thought I needed * Simplify examples * Prep to release v0.0.5 (#2126)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::error::Error;
|
||||
use crate::error::BackendError;
|
||||
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
@@ -34,12 +34,16 @@ pub type FollowEventStreamGetter<Hash> = Box<dyn FnMut() -> FollowEventStreamFut
|
||||
|
||||
/// The future which will return a stream of follow events and the subscription ID for it.
|
||||
pub type FollowEventStreamFut<Hash> = Pin<
|
||||
Box<dyn Future<Output = Result<(FollowEventStream<Hash>, String), Error>> + Send + 'static>,
|
||||
Box<
|
||||
dyn Future<Output = Result<(FollowEventStream<Hash>, String), BackendError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
>;
|
||||
|
||||
/// The stream of follow events.
|
||||
pub type FollowEventStream<Hash> =
|
||||
Pin<Box<dyn Stream<Item = Result<FollowEvent<Hash>, Error>> + Send + 'static>>;
|
||||
Pin<Box<dyn Stream<Item = Result<FollowEvent<Hash>, BackendError>> + Send + 'static>>;
|
||||
|
||||
/// Either a ready message with the current subscription ID, or
|
||||
/// an event from the stream itself.
|
||||
@@ -108,7 +112,7 @@ impl<Hash> FollowStream<Hash> {
|
||||
let stream = methods.chainhead_v1_follow(true).await?;
|
||||
// Extract the subscription ID:
|
||||
let Some(sub_id) = stream.subscription_id().map(ToOwned::to_owned) else {
|
||||
return Err(Error::Other(
|
||||
return Err(BackendError::Other(
|
||||
"Subscription ID expected for chainHead_follow response, but not given"
|
||||
.to_owned(),
|
||||
));
|
||||
@@ -128,7 +132,7 @@ impl<Hash> FollowStream<Hash> {
|
||||
impl<Hash> std::marker::Unpin for FollowStream<Hash> {}
|
||||
|
||||
impl<Hash> Stream for FollowStream<Hash> {
|
||||
type Item = Result<FollowStreamMsg<Hash>, Error>;
|
||||
type Item = Result<FollowStreamMsg<Hash>, BackendError>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let this = self.get_mut();
|
||||
@@ -228,7 +232,7 @@ pub(super) mod test_utils {
|
||||
where
|
||||
Hash: Send + 'static,
|
||||
F: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = Result<FollowEvent<Hash>, Error>>,
|
||||
I: IntoIterator<Item = Result<FollowEvent<Hash>, BackendError>>,
|
||||
{
|
||||
let start_idx = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
@@ -307,7 +311,7 @@ pub mod test {
|
||||
Ok(FollowEvent::Stop),
|
||||
Ok(ev_new_block(1, 2)),
|
||||
// Nothing should be emitted after an error:
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin};
|
||||
use crate::config::Hash;
|
||||
use crate::error::{Error, RpcError};
|
||||
use crate::error::{BackendError, RpcError};
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::ops::DerefMut;
|
||||
@@ -42,7 +42,7 @@ impl<H: Hash> FollowStreamDriver<H> {
|
||||
}
|
||||
|
||||
impl<H: Hash> Stream for FollowStreamDriver<H> {
|
||||
type Item = Result<(), Error>;
|
||||
type Item = Result<(), BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.inner.poll_next_unpin(cx) {
|
||||
@@ -421,7 +421,7 @@ where
|
||||
H: Hash,
|
||||
F: Fn(FollowEvent<BlockRef<H>>) -> Vec<BlockRef<H>>,
|
||||
{
|
||||
type Item = Result<(String, Vec<BlockRef<H>>), Error>;
|
||||
type Item = Result<(String, Vec<BlockRef<H>>), BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if self.is_done {
|
||||
@@ -500,7 +500,7 @@ mod test_utils {
|
||||
where
|
||||
H: Hash + 'static,
|
||||
F: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, Error>>,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, BackendError>>,
|
||||
{
|
||||
let (stream, _) = test_unpin_stream_getter(events, max_life);
|
||||
FollowStreamDriver::new(stream)
|
||||
@@ -537,7 +537,7 @@ mod test {
|
||||
Ok(ev_new_block(0, 1)),
|
||||
Ok(ev_best_block(1)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -580,7 +580,7 @@ mod test {
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -630,7 +630,7 @@ mod test {
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -668,7 +668,7 @@ mod test {
|
||||
Ok(FollowEvent::Stop),
|
||||
Ok(ev_initialized(1)),
|
||||
Ok(ev_finalized([2], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -714,7 +714,7 @@ mod test {
|
||||
// Emulate that we missed some blocks.
|
||||
Ok(ev_initialized(13)),
|
||||
Ok(ev_finalized([14], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -742,7 +742,7 @@ mod test {
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
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"))
|
||||
matches!(&evs[1], Err(BackendError::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(),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use super::ChainHeadRpcMethods;
|
||||
use super::follow_stream::FollowStream;
|
||||
use crate::config::{Config, Hash, HashFor};
|
||||
use crate::error::Error;
|
||||
use crate::error::BackendError;
|
||||
use futures::stream::{FuturesUnordered, Stream, StreamExt};
|
||||
use subxt_rpcs::methods::chain_head::{
|
||||
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock,
|
||||
@@ -71,7 +71,7 @@ pub type UnpinFut = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
|
||||
impl<H: Hash> std::marker::Unpin for FollowStreamUnpin<H> {}
|
||||
|
||||
impl<H: Hash> Stream for FollowStreamUnpin<H> {
|
||||
type Item = Result<FollowStreamMsg<BlockRef<H>>, Error>;
|
||||
type Item = Result<FollowStreamMsg<BlockRef<H>>, BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
@@ -482,7 +482,7 @@ pub(super) mod test_utils {
|
||||
where
|
||||
H: Hash + 'static,
|
||||
F: Fn() -> I + Send + 'static,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, Error>>,
|
||||
I: IntoIterator<Item = Result<FollowEvent<H>, BackendError>>,
|
||||
{
|
||||
// Unpin requests will come here so that we can look out for them.
|
||||
let (unpin_tx, unpin_rx) = std::sync::mpsc::channel();
|
||||
@@ -567,7 +567,7 @@ mod test {
|
||||
Ok(ev_new_block(0, 1)),
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_new_block(2, 3)),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -593,7 +593,7 @@ mod test {
|
||||
[
|
||||
Ok(ev_initialized(0)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
3,
|
||||
@@ -624,7 +624,7 @@ mod test {
|
||||
Ok(ev_finalized([3], [])),
|
||||
Ok(ev_finalized([4], [])),
|
||||
Ok(ev_finalized([5], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
3,
|
||||
@@ -663,7 +663,7 @@ mod test {
|
||||
Ok(ev_new_block(1, 2)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_finalized([2], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -711,7 +711,7 @@ mod test {
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_finalized([2], [3])),
|
||||
Ok(ev_finalized([4], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
@@ -771,7 +771,7 @@ mod test {
|
||||
Ok(ev_best_block(1)),
|
||||
Ok(ev_finalized([1], [])),
|
||||
Ok(ev_finalized([2], [])),
|
||||
Err(Error::Other("ended".to_owned())),
|
||||
Err(BackendError::Other("ended".to_owned())),
|
||||
]
|
||||
},
|
||||
10,
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::backend::{
|
||||
TransactionStatus, utils::retry,
|
||||
};
|
||||
use crate::config::{Config, Hash, HashFor};
|
||||
use crate::error::{Error, RpcError};
|
||||
use crate::error::{BackendError, RpcError};
|
||||
use async_trait::async_trait;
|
||||
use follow_stream_driver::{FollowStreamDriver, FollowStreamDriverHandle};
|
||||
use futures::future::Either;
|
||||
@@ -229,7 +229,7 @@ impl<T: Config> ChainHeadBackend<T> {
|
||||
async fn stream_headers<F>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>
|
||||
where
|
||||
F: Fn(
|
||||
FollowEvent<follow_stream_unpin::BlockRef<HashFor<T>>>,
|
||||
@@ -290,7 +290,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
&self,
|
||||
keys: Vec<Vec<u8>>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
retry(|| async {
|
||||
let queries = keys.iter().map(|key| StorageQuery {
|
||||
key: &**key,
|
||||
@@ -325,7 +325,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<Vec<u8>>, Error> {
|
||||
) -> Result<StreamOfResults<Vec<u8>>, BackendError> {
|
||||
retry(|| async {
|
||||
// Ask for hashes, and then just ignore them and return the keys that come back.
|
||||
let query = StorageQuery {
|
||||
@@ -351,7 +351,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
retry(|| async {
|
||||
let query = StorageQuery {
|
||||
key: &*key,
|
||||
@@ -386,7 +386,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, Error> {
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError> {
|
||||
retry(|| async {
|
||||
let genesis_hash = self.methods.chainspec_v1_genesis_hash().await?;
|
||||
Ok(genesis_hash)
|
||||
@@ -394,7 +394,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error> {
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError> {
|
||||
retry(|| async {
|
||||
let sub_id = get_subscription_id(&self.follow_handle).await?;
|
||||
let header = self.methods.chainhead_v1_header(&sub_id, at).await?;
|
||||
@@ -403,7 +403,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error> {
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
|
||||
retry(|| async {
|
||||
let sub_id = get_subscription_id(&self.follow_handle).await?;
|
||||
|
||||
@@ -432,7 +432,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error> {
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError> {
|
||||
let next_ref: Option<BlockRef<HashFor<T>>> = self
|
||||
.follow_handle
|
||||
.subscribe()
|
||||
@@ -452,17 +452,19 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
next_ref.ok_or_else(|| RpcError::SubscriptionDropped.into())
|
||||
}
|
||||
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError> {
|
||||
// Just start a stream of version infos, and return the first value we get from it.
|
||||
let runtime_version = self.stream_runtime_version().await?.next().await;
|
||||
match runtime_version {
|
||||
None => Err(Error::Rpc(RpcError::SubscriptionDropped)),
|
||||
None => Err(BackendError::Rpc(RpcError::SubscriptionDropped)),
|
||||
Some(Err(e)) => Err(e),
|
||||
Some(Ok(version)) => Ok(version),
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
|
||||
async fn stream_runtime_version(
|
||||
&self,
|
||||
) -> Result<StreamOfResults<RuntimeVersion>, BackendError> {
|
||||
// Keep track of runtime details announced in new blocks, and then when blocks
|
||||
// are finalized, find the latest of these that has runtime details, and clear the rest.
|
||||
let mut runtimes = HashMap::new();
|
||||
@@ -526,7 +528,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
|
||||
let runtime_details = match runtime_event {
|
||||
RuntimeEvent::Invalid(err) => {
|
||||
return std::future::ready(Some(Err(Error::Other(err.error))))
|
||||
return std::future::ready(Some(Err(BackendError::Other(format!("Invalid runtime error using chainHead RPCs: {}", err.error)))))
|
||||
}
|
||||
RuntimeEvent::Valid(ev) => ev,
|
||||
};
|
||||
@@ -544,7 +546,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn stream_all_block_headers(
|
||||
&self,
|
||||
_hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
// TODO: https://github.com/paritytech/subxt/issues/1568
|
||||
//
|
||||
// It's possible that blocks may be silently missed if
|
||||
@@ -562,7 +564,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn stream_best_block_headers(
|
||||
&self,
|
||||
_hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
// TODO: https://github.com/paritytech/subxt/issues/1568
|
||||
//
|
||||
// It's possible that blocks may be silently missed if
|
||||
@@ -578,7 +580,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn stream_finalized_block_headers(
|
||||
&self,
|
||||
_hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
self.stream_headers(|ev| match ev {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes,
|
||||
FollowEvent::Finalized(ev) => ev.finalized_block_hashes,
|
||||
@@ -590,12 +592,12 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
async fn submit_transaction(
|
||||
&self,
|
||||
extrinsic: &[u8],
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
// Submit a transaction. This makes no attempt to sync with follow events,
|
||||
async fn submit_transaction_ignoring_follow_events<T: Config>(
|
||||
extrinsic: &[u8],
|
||||
methods: &ChainHeadRpcMethods<T>,
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
let tx_progress = methods
|
||||
.transactionwatch_v1_submit_and_watch(extrinsic)
|
||||
.await?
|
||||
@@ -637,7 +639,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
transaction_timeout_secs: u64,
|
||||
methods: &ChainHeadRpcMethods<T>,
|
||||
follow_handle: &FollowStreamDriverHandle<HashFor<T>>,
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
// We care about new and finalized block hashes.
|
||||
enum SeenBlockMarker {
|
||||
New,
|
||||
@@ -664,7 +666,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
let start_instant = web_time::Instant::now();
|
||||
|
||||
// A quick helper to return a generic error.
|
||||
let err_other = |s: &str| Some(Err(Error::Other(s.into())));
|
||||
let err_other = |s: &str| Some(Err(BackendError::Other(s.into())));
|
||||
|
||||
// Now we can attempt to associate tx events with pinned blocks.
|
||||
let tx_stream = futures::stream::poll_fn(move |cx| {
|
||||
@@ -828,7 +830,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
) -> Result<Vec<u8>, BackendError> {
|
||||
retry(|| async {
|
||||
let sub_id = get_subscription_id(&self.follow_handle).await?;
|
||||
|
||||
@@ -867,7 +869,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
|
||||
/// A helper to obtain a subscription ID.
|
||||
async fn get_subscription_id<H: Hash>(
|
||||
follow_handle: &FollowStreamDriverHandle<H>,
|
||||
) -> Result<String, Error> {
|
||||
) -> Result<String, BackendError> {
|
||||
let Some(sub_id) = follow_handle.subscribe().subscription_id().await else {
|
||||
return Err(RpcError::SubscriptionDropped.into());
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use super::follow_stream_driver::FollowStreamDriverHandle;
|
||||
use super::follow_stream_unpin::BlockRef;
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::error::{Error, RpcError};
|
||||
use crate::error::{BackendError, RpcError};
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use std::collections::VecDeque;
|
||||
use std::future::Future;
|
||||
@@ -36,7 +36,7 @@ impl<T: Config> StorageItems<T> {
|
||||
at: HashFor<T>,
|
||||
follow_handle: &FollowStreamDriverHandle<HashFor<T>>,
|
||||
methods: ChainHeadRpcMethods<T>,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self, BackendError> {
|
||||
let sub_id = super::get_subscription_id(follow_handle).await?;
|
||||
|
||||
// Subscribe to events and make the initial request to get an operation ID.
|
||||
@@ -92,10 +92,10 @@ impl<T: Config> StorageItems<T> {
|
||||
pub type FollowEventStream<Hash> =
|
||||
Pin<Box<dyn Stream<Item = FollowEvent<BlockRef<Hash>>> + Send + 'static>>;
|
||||
pub type ContinueFutGetter = Box<dyn Fn() -> ContinueFut + Send + 'static>;
|
||||
pub type ContinueFut = Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'static>>;
|
||||
pub type ContinueFut = Pin<Box<dyn Future<Output = Result<(), BackendError>> + Send + 'static>>;
|
||||
|
||||
impl<T: Config> Stream for StorageItems<T> {
|
||||
type Item = Result<StorageResult, Error>;
|
||||
type Item = Result<StorageResult, BackendError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
loop {
|
||||
@@ -157,7 +157,7 @@ impl<T: Config> Stream for StorageItems<T> {
|
||||
FollowEvent::OperationError(err) if err.operation_id == *self.operation_id => {
|
||||
// Something went wrong obtaining storage items; mark as done and return the error.
|
||||
self.done = true;
|
||||
return Poll::Ready(Some(Err(Error::Other(err.error))));
|
||||
return Poll::Ready(Some(Err(BackendError::Other(err.error))));
|
||||
}
|
||||
_ => {
|
||||
// We don't care about this event; wait for the next.
|
||||
|
||||
+27
-26
@@ -11,10 +11,8 @@ use crate::backend::{
|
||||
Backend, BlockRef, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults,
|
||||
TransactionStatus,
|
||||
};
|
||||
use crate::{
|
||||
Error,
|
||||
config::{Config, HashFor, Header},
|
||||
};
|
||||
use crate::config::{Config, HashFor, Header};
|
||||
use crate::error::BackendError;
|
||||
use async_trait::async_trait;
|
||||
use futures::TryStreamExt;
|
||||
use futures::{Future, FutureExt, Stream, StreamExt, future, future::Either, stream};
|
||||
@@ -101,12 +99,12 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
&self,
|
||||
keys: Vec<Vec<u8>>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
fn get_entry<T: Config>(
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
methods: LegacyRpcMethods<T>,
|
||||
) -> impl Future<Output = Result<Option<StorageResponse>, Error>> {
|
||||
) -> impl Future<Output = Result<Option<StorageResponse>, BackendError>> {
|
||||
retry(move || {
|
||||
let methods = methods.clone();
|
||||
let key = key.clone();
|
||||
@@ -138,7 +136,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<Vec<u8>>, Error> {
|
||||
) -> Result<StreamOfResults<Vec<u8>>, BackendError> {
|
||||
let keys = StorageFetchDescendantKeysStream {
|
||||
at,
|
||||
key,
|
||||
@@ -169,7 +167,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error> {
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
|
||||
let keys_stream = StorageFetchDescendantKeysStream {
|
||||
at,
|
||||
key,
|
||||
@@ -187,7 +185,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
})))
|
||||
}
|
||||
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, Error> {
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError> {
|
||||
retry(|| async {
|
||||
let hash = self.methods.genesis_hash().await?;
|
||||
Ok(hash)
|
||||
@@ -195,7 +193,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error> {
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError> {
|
||||
retry(|| async {
|
||||
let header = self.methods.chain_get_header(Some(at)).await?;
|
||||
Ok(header)
|
||||
@@ -203,7 +201,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error> {
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
|
||||
retry(|| async {
|
||||
let Some(details) = self.methods.chain_get_block(Some(at)).await? else {
|
||||
return Ok(None);
|
||||
@@ -215,7 +213,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error> {
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError> {
|
||||
retry(|| async {
|
||||
let hash = self.methods.chain_get_finalized_head().await?;
|
||||
Ok(BlockRef::from_hash(hash))
|
||||
@@ -223,7 +221,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError> {
|
||||
retry(|| async {
|
||||
let details = self.methods.state_get_runtime_version(None).await?;
|
||||
Ok(RuntimeVersion {
|
||||
@@ -234,7 +232,9 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
|
||||
async fn stream_runtime_version(
|
||||
&self,
|
||||
) -> Result<StreamOfResults<RuntimeVersion>, BackendError> {
|
||||
let methods = self.methods.clone();
|
||||
|
||||
let retry_sub = retry_stream(move || {
|
||||
@@ -274,7 +274,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn stream_all_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
let methods = self.methods.clone();
|
||||
let retry_sub = retry_stream(move || {
|
||||
let methods = methods.clone();
|
||||
@@ -297,7 +297,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn stream_best_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
let methods = self.methods.clone();
|
||||
|
||||
let retry_sub = retry_stream(move || {
|
||||
@@ -321,7 +321,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn stream_finalized_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
let this = self.clone();
|
||||
|
||||
let retry_sub = retry_stream(move || {
|
||||
@@ -361,7 +361,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
async fn submit_transaction(
|
||||
&self,
|
||||
extrinsic: &[u8],
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
|
||||
let sub = self
|
||||
.methods
|
||||
.author_submit_and_watch_extrinsic(extrinsic)
|
||||
@@ -423,7 +423,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
) -> Result<Vec<u8>, BackendError> {
|
||||
retry(|| async {
|
||||
let res = self
|
||||
.methods
|
||||
@@ -442,11 +442,11 @@ pub fn subscribe_to_block_headers_filling_in_gaps<T, S, E>(
|
||||
methods: LegacyRpcMethods<T>,
|
||||
sub: S,
|
||||
mut last_block_num: Option<u64>,
|
||||
) -> impl Stream<Item = Result<T::Header, Error>> + Send
|
||||
) -> impl Stream<Item = Result<T::Header, BackendError>> + Send
|
||||
where
|
||||
T: Config,
|
||||
S: Stream<Item = Result<T::Header, E>> + Send,
|
||||
E: Into<Error> + Send + 'static,
|
||||
E: Into<BackendError> + Send + 'static,
|
||||
{
|
||||
sub.flat_map(move |s| {
|
||||
// Get the header, or return a stream containing just the error.
|
||||
@@ -470,7 +470,7 @@ where
|
||||
async move {
|
||||
let hash = methods.chain_get_block_hash(Some(n.into())).await?;
|
||||
let header = methods.chain_get_header(hash).await?;
|
||||
Ok::<_, Error>(header)
|
||||
Ok::<_, BackendError>(header)
|
||||
}
|
||||
})
|
||||
.filter_map(async |h| h.transpose());
|
||||
@@ -495,7 +495,8 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
|
||||
// What key do we start paginating from? None = from the beginning.
|
||||
pagination_start_key: Option<Vec<u8>>,
|
||||
// Keys, future and cached:
|
||||
keys_fut: Option<Pin<Box<dyn Future<Output = Result<Vec<Vec<u8>>, Error>> + Send + 'static>>>,
|
||||
keys_fut:
|
||||
Option<Pin<Box<dyn Future<Output = Result<Vec<Vec<u8>>, BackendError>> + Send + 'static>>>,
|
||||
// Set to true when we're done:
|
||||
done: bool,
|
||||
}
|
||||
@@ -503,7 +504,7 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
|
||||
impl<T: Config> std::marker::Unpin for StorageFetchDescendantKeysStream<T> {}
|
||||
|
||||
impl<T: Config> Stream for StorageFetchDescendantKeysStream<T> {
|
||||
type Item = Result<Vec<Vec<u8>>, Error>;
|
||||
type Item = Result<Vec<Vec<u8>>, BackendError>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
loop {
|
||||
@@ -584,7 +585,7 @@ pub struct StorageFetchDescendantValuesStream<T: Config> {
|
||||
results_fut: Option<
|
||||
Pin<
|
||||
Box<
|
||||
dyn Future<Output = Result<Option<VecDeque<(Vec<u8>, Vec<u8>)>>, Error>>
|
||||
dyn Future<Output = Result<Option<VecDeque<(Vec<u8>, Vec<u8>)>>, BackendError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
@@ -595,7 +596,7 @@ pub struct StorageFetchDescendantValuesStream<T: Config> {
|
||||
}
|
||||
|
||||
impl<T: Config> Stream for StorageFetchDescendantValuesStream<T> {
|
||||
type Item = Result<StorageResponse, Error>;
|
||||
type Item = Result<StorageResponse, BackendError>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.as_mut();
|
||||
loop {
|
||||
|
||||
+39
-32
@@ -11,14 +11,14 @@ pub mod legacy;
|
||||
pub mod utils;
|
||||
|
||||
use crate::config::{Config, HashFor};
|
||||
use crate::error::Error;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::error::BackendError;
|
||||
use async_trait::async_trait;
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use subxt_core::client::RuntimeVersion;
|
||||
use subxt_metadata::Metadata;
|
||||
|
||||
/// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`].
|
||||
pub mod rpc {
|
||||
@@ -82,66 +82,67 @@ pub trait Backend<T: Config>: sealed::Sealed + Send + Sync + 'static {
|
||||
&self,
|
||||
keys: Vec<Vec<u8>>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error>;
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError>;
|
||||
|
||||
/// Fetch keys underneath the given key from storage.
|
||||
async fn storage_fetch_descendant_keys(
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<Vec<u8>>, Error>;
|
||||
) -> Result<StreamOfResults<Vec<u8>>, BackendError>;
|
||||
|
||||
/// Fetch values underneath the given key from storage.
|
||||
async fn storage_fetch_descendant_values(
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<StreamOfResults<StorageResponse>, Error>;
|
||||
) -> Result<StreamOfResults<StorageResponse>, BackendError>;
|
||||
|
||||
/// Fetch the genesis hash
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, Error>;
|
||||
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError>;
|
||||
|
||||
/// Get a block header
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error>;
|
||||
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError>;
|
||||
|
||||
/// Return the extrinsics found in the block. Each extrinsic is represented
|
||||
/// by a vector of bytes which has _not_ been SCALE decoded (in other words, the
|
||||
/// first bytes in the vector will decode to the compact encoded length of the extrinsic)
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error>;
|
||||
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError>;
|
||||
|
||||
/// Get the most recent finalized block hash.
|
||||
/// Note: needed only in blocks client for finalized block stream; can prolly be removed.
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error>;
|
||||
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError>;
|
||||
|
||||
/// Get information about the current runtime.
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error>;
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError>;
|
||||
|
||||
/// A stream of all new runtime versions as they occur.
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error>;
|
||||
async fn stream_runtime_version(&self)
|
||||
-> Result<StreamOfResults<RuntimeVersion>, BackendError>;
|
||||
|
||||
/// A stream of all new block headers as they arrive.
|
||||
async fn stream_all_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
|
||||
|
||||
/// A stream of best block headers.
|
||||
async fn stream_best_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
|
||||
|
||||
/// A stream of finalized block headers.
|
||||
async fn stream_finalized_block_headers(
|
||||
&self,
|
||||
hasher: T::Hasher,
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
|
||||
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
|
||||
|
||||
/// Submit a transaction. This will return a stream of events about it.
|
||||
async fn submit_transaction(
|
||||
&self,
|
||||
bytes: &[u8],
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error>;
|
||||
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError>;
|
||||
|
||||
/// Make a call to some runtime API.
|
||||
async fn call(
|
||||
@@ -149,7 +150,7 @@ pub trait Backend<T: Config>: sealed::Sealed + Send + Sync + 'static {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error>;
|
||||
) -> Result<Vec<u8>, BackendError>;
|
||||
}
|
||||
|
||||
/// helpful utility methods derived from those provided on [`Backend`]
|
||||
@@ -160,7 +161,7 @@ pub trait BackendExt<T: Config>: Backend<T> {
|
||||
&self,
|
||||
key: Vec<u8>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Option<Vec<u8>>, Error> {
|
||||
) -> Result<Option<Vec<u8>>, BackendError> {
|
||||
self.storage_fetch_values(vec![key], at)
|
||||
.await?
|
||||
.next()
|
||||
@@ -176,32 +177,39 @@ pub trait BackendExt<T: Config>: Backend<T> {
|
||||
method: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: HashFor<T>,
|
||||
) -> Result<D, Error> {
|
||||
) -> Result<D, BackendError> {
|
||||
let bytes = self.call(method, call_parameters, at).await?;
|
||||
let res = D::decode(&mut &*bytes)?;
|
||||
let res =
|
||||
D::decode(&mut &*bytes).map_err(BackendError::CouldNotScaleDecodeRuntimeResponse)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Return the metadata at some version.
|
||||
async fn metadata_at_version(&self, version: u32, at: HashFor<T>) -> Result<Metadata, Error> {
|
||||
async fn metadata_at_version(
|
||||
&self,
|
||||
version: u32,
|
||||
at: HashFor<T>,
|
||||
) -> Result<Metadata, BackendError> {
|
||||
let param = version.encode();
|
||||
|
||||
let opaque: Option<frame_metadata::OpaqueMetadata> = self
|
||||
.call_decoding("Metadata_metadata_at_version", Some(¶m), at)
|
||||
.await?;
|
||||
let Some(opaque) = opaque else {
|
||||
return Err(Error::Other("Metadata version not found".into()));
|
||||
return Err(BackendError::MetadataVersionNotFound(version));
|
||||
};
|
||||
|
||||
let metadata: Metadata = Decode::decode(&mut &opaque.0[..])?;
|
||||
let metadata: Metadata =
|
||||
Decode::decode(&mut &opaque.0[..]).map_err(BackendError::CouldNotDecodeMetadata)?;
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Return V14 metadata from the legacy `Metadata_metadata` call.
|
||||
async fn legacy_metadata(&self, at: HashFor<T>) -> Result<Metadata, Error> {
|
||||
async fn legacy_metadata(&self, at: HashFor<T>) -> Result<Metadata, BackendError> {
|
||||
let opaque: frame_metadata::OpaqueMetadata =
|
||||
self.call_decoding("Metadata_metadata", None, at).await?;
|
||||
let metadata: Metadata = Decode::decode(&mut &opaque.0[..])?;
|
||||
let metadata: Metadata =
|
||||
Decode::decode(&mut &opaque.0[..]).map_err(BackendError::CouldNotDecodeMetadata)?;
|
||||
Ok(metadata)
|
||||
}
|
||||
}
|
||||
@@ -325,8 +333,8 @@ impl<T> StreamOf<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream of [`Result<Item, Error>`].
|
||||
pub type StreamOfResults<T> = StreamOf<Result<T, Error>>;
|
||||
/// A stream of [`Result<Item, BackendError>`].
|
||||
pub type StreamOfResults<T> = StreamOf<Result<T, BackendError>>;
|
||||
|
||||
/// The status of the transaction.
|
||||
///
|
||||
@@ -541,7 +549,7 @@ mod test {
|
||||
/// - `call`
|
||||
/// The test covers them because they follow the simple pattern of:
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// async fn THE_THING(&self) -> Result<HashFor<T>, Error> {
|
||||
/// async fn THE_THING(&self) -> Result<HashFor<T>, BackendError> {
|
||||
/// retry(|| <DO THE THING> ).await
|
||||
/// }
|
||||
/// ```
|
||||
@@ -574,7 +582,7 @@ mod test {
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// async fn stream_the_thing(
|
||||
/// &self,
|
||||
/// ) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
|
||||
/// ) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
|
||||
/// let methods = self.methods.clone();
|
||||
/// let retry_sub = retry_stream(move || {
|
||||
/// let methods = methods.clone();
|
||||
@@ -635,7 +643,7 @@ mod test {
|
||||
);
|
||||
assert!(matches!(
|
||||
results.next().await.unwrap(),
|
||||
Err(Error::Rpc(RpcError::ClientError(
|
||||
Err(BackendError::Rpc(RpcError::ClientError(
|
||||
subxt_rpcs::Error::Client(_)
|
||||
)))
|
||||
));
|
||||
@@ -644,7 +652,6 @@ mod test {
|
||||
}
|
||||
|
||||
mod unstable_backend {
|
||||
use crate::error::RpcError;
|
||||
use subxt_rpcs::methods::chain_head::{
|
||||
self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError,
|
||||
OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent,
|
||||
@@ -858,7 +865,7 @@ mod test {
|
||||
.next()
|
||||
.await
|
||||
.unwrap()
|
||||
.is_err_and(|e| matches!(e, Error::Other(e) if e == "error"))
|
||||
.is_err_and(|e| matches!(e, BackendError::Other(e) if e == "error"))
|
||||
);
|
||||
assert!(response.next().await.is_none());
|
||||
}
|
||||
@@ -1047,7 +1054,7 @@ mod test {
|
||||
let response = backend
|
||||
.storage_fetch_values(["ID1".into()].into(), random_hash())
|
||||
.await;
|
||||
assert!(matches!(response, Err(Error::Rpc(RpcError::LimitReached))));
|
||||
assert!(matches!(response, Err(e) if e.is_rpc_limit_reached()));
|
||||
|
||||
// Advance the driver until a new chainHead_follow subscription has been started up.
|
||||
let _ = driver.next().await.unwrap();
|
||||
|
||||
+15
-14
@@ -1,7 +1,7 @@
|
||||
//! RPC utils.
|
||||
|
||||
use super::{StreamOf, StreamOfResults};
|
||||
use crate::error::Error;
|
||||
use crate::error::BackendError;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, Stream, StreamExt};
|
||||
use std::{future::Future, pin::Pin, task::Poll};
|
||||
@@ -10,10 +10,11 @@ use std::{future::Future, pin::Pin, task::Poll};
|
||||
type ResubscribeGetter<T> = Box<dyn FnMut() -> ResubscribeFuture<T> + Send>;
|
||||
|
||||
/// Future that resolves to a subscription stream.
|
||||
type ResubscribeFuture<T> = Pin<Box<dyn Future<Output = Result<StreamOfResults<T>, Error>> + Send>>;
|
||||
type ResubscribeFuture<T> =
|
||||
Pin<Box<dyn Future<Output = Result<StreamOfResults<T>, BackendError>> + Send>>;
|
||||
|
||||
pub(crate) enum PendingOrStream<T> {
|
||||
Pending(BoxFuture<'static, Result<StreamOfResults<T>, Error>>),
|
||||
Pending(BoxFuture<'static, Result<StreamOfResults<T>, BackendError>>),
|
||||
Stream(StreamOfResults<T>),
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ struct RetrySubscription<T> {
|
||||
impl<T> std::marker::Unpin for RetrySubscription<T> {}
|
||||
|
||||
impl<T> Stream for RetrySubscription<T> {
|
||||
type Item = Result<T, Error>;
|
||||
type Item = Result<T, BackendError>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
@@ -92,7 +93,7 @@ impl<T> Stream for RetrySubscription<T> {
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// use subxt::backend::utils::retry;
|
||||
///
|
||||
/// async fn some_future() -> Result<(), subxt::error::Error> {
|
||||
/// async fn some_future() -> Result<(), subxt::error::BackendError> {
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
@@ -101,10 +102,10 @@ impl<T> Stream for RetrySubscription<T> {
|
||||
/// let result = retry(|| some_future()).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn retry<T, F, R>(mut retry_future: F) -> Result<R, Error>
|
||||
pub async fn retry<T, F, R>(mut retry_future: F) -> Result<R, BackendError>
|
||||
where
|
||||
F: FnMut() -> T,
|
||||
T: Future<Output = Result<R, Error>>,
|
||||
T: Future<Output = Result<R, BackendError>>,
|
||||
{
|
||||
const REJECTED_MAX_RETRIES: usize = 10;
|
||||
let mut rejected_retries = 0;
|
||||
@@ -163,7 +164,7 @@ where
|
||||
/// }).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn retry_stream<F, R>(sub_stream: F) -> Result<StreamOfResults<R>, Error>
|
||||
pub async fn retry_stream<F, R>(sub_stream: F) -> Result<StreamOfResults<R>, BackendError>
|
||||
where
|
||||
F: FnMut() -> ResubscribeFuture<R> + Send + 'static + Clone,
|
||||
R: Send + 'static,
|
||||
@@ -187,12 +188,12 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::backend::StreamOf;
|
||||
|
||||
fn disconnect_err() -> Error {
|
||||
Error::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into())
|
||||
fn disconnect_err() -> BackendError {
|
||||
BackendError::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into())
|
||||
}
|
||||
|
||||
fn custom_err() -> Error {
|
||||
Error::Other(String::new())
|
||||
fn custom_err() -> BackendError {
|
||||
BackendError::Other(String::new())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -213,7 +214,7 @@ mod tests {
|
||||
|
||||
let result = retry_stream
|
||||
.take(5)
|
||||
.collect::<Vec<Result<usize, Error>>>()
|
||||
.collect::<Vec<Result<usize, BackendError>>>()
|
||||
.await;
|
||||
|
||||
assert!(matches!(result[0], Ok(r) if r == 1));
|
||||
@@ -270,6 +271,6 @@ mod tests {
|
||||
|
||||
assert!(matches!(result[0], Ok(r) if r == 1));
|
||||
assert!(matches!(result[1], Err(ref e) if e.is_disconnected_will_reconnect()));
|
||||
assert!(matches!(result[2], Err(ref e) if matches!(e, Error::Other(_))));
|
||||
assert!(matches!(result[2], Err(ref e) if matches!(e, BackendError::Other(_))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ use crate::{
|
||||
blocks::Extrinsics,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor, Header},
|
||||
error::{BlockError, DecodeError, Error},
|
||||
error::{AccountNonceError, BlockError, EventsError, ExtrinsicError},
|
||||
events,
|
||||
runtime_api::RuntimeApi,
|
||||
storage::Storage,
|
||||
storage::StorageClientAt,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
@@ -84,38 +84,51 @@ where
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// Return the events associated with the block, fetching them from the node if necessary.
|
||||
pub async fn events(&self) -> Result<events::Events<T>, Error> {
|
||||
pub async fn events(&self) -> Result<events::Events<T>, EventsError> {
|
||||
get_events(&self.client, self.hash(), &self.cached_events).await
|
||||
}
|
||||
|
||||
/// Fetch and return the extrinsics in the block body.
|
||||
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, Error> {
|
||||
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, ExtrinsicError> {
|
||||
let block_hash = self.hash();
|
||||
let Some(extrinsics) = self.client.backend().block_body(block_hash).await? else {
|
||||
return Err(BlockError::not_found(block_hash).into());
|
||||
};
|
||||
|
||||
Extrinsics::new(
|
||||
let extrinsics = self
|
||||
.client
|
||||
.backend()
|
||||
.block_body(block_hash)
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetBlockBody)?
|
||||
.ok_or_else(|| ExtrinsicError::BlockNotFound(block_hash.into()))?;
|
||||
|
||||
let extrinsics = Extrinsics::new(
|
||||
self.client.clone(),
|
||||
extrinsics,
|
||||
self.cached_events.clone(),
|
||||
block_hash,
|
||||
)
|
||||
)?;
|
||||
|
||||
Ok(extrinsics)
|
||||
}
|
||||
|
||||
/// Work with storage.
|
||||
pub fn storage(&self) -> Storage<T, C> {
|
||||
Storage::new(self.client.clone(), self.block_ref.clone())
|
||||
pub fn storage(&self) -> StorageClientAt<T, C> {
|
||||
StorageClientAt::new(self.client.clone(), self.block_ref.clone())
|
||||
}
|
||||
|
||||
/// Execute a runtime API call at this block.
|
||||
pub async fn runtime_api(&self) -> Result<RuntimeApi<T, C>, Error> {
|
||||
Ok(RuntimeApi::new(self.client.clone(), self.block_ref.clone()))
|
||||
pub async fn runtime_api(&self) -> RuntimeApi<T, C> {
|
||||
RuntimeApi::new(self.client.clone(), self.block_ref.clone())
|
||||
}
|
||||
|
||||
/// Get the account nonce for a given account ID at this block.
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
|
||||
get_account_nonce(&self.client, account_id, self.hash()).await
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, BlockError> {
|
||||
get_account_nonce(&self.client, account_id, self.hash())
|
||||
.await
|
||||
.map_err(|e| BlockError::AccountNonceError {
|
||||
block_hash: self.hash().into(),
|
||||
account_id: account_id.encode().into(),
|
||||
reason: e,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +137,7 @@ pub(crate) async fn get_events<C, T>(
|
||||
client: &C,
|
||||
block_hash: HashFor<T>,
|
||||
cached_events: &AsyncMutex<Option<events::Events<T>>>,
|
||||
) -> Result<events::Events<T>, Error>
|
||||
) -> Result<events::Events<T>, EventsError>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
@@ -152,7 +165,7 @@ pub(crate) async fn get_account_nonce<C, T>(
|
||||
client: &C,
|
||||
account_id: &T::AccountId,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<u64, Error>
|
||||
) -> Result<u64, AccountNonceError>
|
||||
where
|
||||
C: OnlineClientT<T>,
|
||||
T: Config,
|
||||
@@ -173,10 +186,9 @@ where
|
||||
4 => u32::decode(cursor)?.into(),
|
||||
8 => u64::decode(cursor)?,
|
||||
_ => {
|
||||
return Err(Error::Decode(DecodeError::custom_string(format!(
|
||||
"state call AccountNonceApi_account_nonce returned an unexpected number of bytes: {} (expected 2, 4 or 8)",
|
||||
account_nonce_bytes.len()
|
||||
))));
|
||||
return Err(AccountNonceError::WrongNumberOfBytes(
|
||||
account_nonce_bytes.len(),
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(account_nonce)
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
backend::{BlockRef, StreamOfResults},
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::{BlockError, Error},
|
||||
error::BlockError,
|
||||
utils::PhantomDataSendSync,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
@@ -15,7 +15,7 @@ use futures::StreamExt;
|
||||
use std::future::Future;
|
||||
|
||||
type BlockStream<T> = StreamOfResults<T>;
|
||||
type BlockStreamRes<T> = Result<BlockStream<T>, Error>;
|
||||
type BlockStreamRes<T> = Result<BlockStream<T>, BlockError>;
|
||||
|
||||
/// A client for working with blocks.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -49,14 +49,14 @@ where
|
||||
pub fn at(
|
||||
&self,
|
||||
block_ref: impl Into<BlockRef<HashFor<T>>>,
|
||||
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
|
||||
self.at_or_latest(Some(block_ref.into()))
|
||||
}
|
||||
|
||||
/// Obtain block details of the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
|
||||
self.at_or_latest(None)
|
||||
}
|
||||
|
||||
@@ -65,18 +65,35 @@ where
|
||||
fn at_or_latest(
|
||||
&self,
|
||||
block_ref: Option<BlockRef<HashFor<T>>>,
|
||||
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
// If a block ref isn't provided, we'll get the latest finalized ref to use.
|
||||
let block_ref = match block_ref {
|
||||
Some(r) => r,
|
||||
None => client.backend().latest_finalized_block_ref().await?,
|
||||
None => client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(BlockError::CouldNotGetLatestBlock)?,
|
||||
};
|
||||
|
||||
let block_header = match client.backend().block_header(block_ref.hash()).await? {
|
||||
let maybe_block_header = client
|
||||
.backend()
|
||||
.block_header(block_ref.hash())
|
||||
.await
|
||||
.map_err(|e| BlockError::CouldNotGetBlockHeader {
|
||||
block_hash: block_ref.hash().into(),
|
||||
reason: e,
|
||||
})?;
|
||||
|
||||
let block_header = match maybe_block_header {
|
||||
Some(header) => header,
|
||||
None => return Err(BlockError::not_found(block_ref.hash()).into()),
|
||||
None => {
|
||||
return Err(BlockError::BlockNotFound {
|
||||
block_hash: block_ref.hash().into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Block::new(block_header, block_ref, client))
|
||||
@@ -89,14 +106,18 @@ where
|
||||
/// the time.
|
||||
pub fn subscribe_all(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let hasher = client.hasher();
|
||||
header_sub_fut_to_block_sub(self.clone(), async move {
|
||||
let stream = client.backend().stream_all_block_headers(hasher).await?;
|
||||
let stream = client
|
||||
.backend()
|
||||
.stream_all_block_headers(hasher)
|
||||
.await
|
||||
.map_err(BlockError::CouldNotSubscribeToAllBlocks)?;
|
||||
BlockStreamRes::Ok(stream)
|
||||
})
|
||||
}
|
||||
@@ -107,14 +128,18 @@ where
|
||||
/// the time.
|
||||
pub fn subscribe_best(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let hasher = client.hasher();
|
||||
header_sub_fut_to_block_sub(self.clone(), async move {
|
||||
let stream = client.backend().stream_best_block_headers(hasher).await?;
|
||||
let stream = client
|
||||
.backend()
|
||||
.stream_best_block_headers(hasher)
|
||||
.await
|
||||
.map_err(BlockError::CouldNotSubscribeToBestBlocks)?;
|
||||
BlockStreamRes::Ok(stream)
|
||||
})
|
||||
}
|
||||
@@ -122,7 +147,7 @@ where
|
||||
/// Subscribe to finalized blocks.
|
||||
pub fn subscribe_finalized(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
|
||||
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
|
||||
where
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
@@ -132,7 +157,8 @@ where
|
||||
let stream = client
|
||||
.backend()
|
||||
.stream_finalized_block_headers(hasher)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(BlockError::CouldNotSubscribeToFinalizedBlocks)?;
|
||||
BlockStreamRes::Ok(stream)
|
||||
})
|
||||
}
|
||||
@@ -143,10 +169,10 @@ where
|
||||
async fn header_sub_fut_to_block_sub<T, Client, S>(
|
||||
blocks_client: BlocksClient<T, Client>,
|
||||
sub: S,
|
||||
) -> Result<BlockStream<Block<T, Client>>, Error>
|
||||
) -> Result<BlockStream<Block<T, Client>>, BlockError>
|
||||
where
|
||||
T: Config,
|
||||
S: Future<Output = Result<BlockStream<(T::Header, BlockRef<HashFor<T>>)>, Error>>
|
||||
S: Future<Output = Result<BlockStream<(T::Header, BlockRef<HashFor<T>>)>, BlockError>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
Client: OnlineClientT<T> + Send + Sync + 'static,
|
||||
|
||||
@@ -6,18 +6,16 @@ use crate::{
|
||||
blocks::block_types::{CachedEvents, get_events},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::{EventsError, ExtrinsicDecodeErrorAt, ExtrinsicError},
|
||||
events,
|
||||
};
|
||||
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
use subxt_core::blocks::{ExtrinsicDetails as CoreExtrinsicDetails, Extrinsics as CoreExtrinsics};
|
||||
|
||||
// Re-export anything that's directly returned/used in the APIs below.
|
||||
pub use subxt_core::blocks::{
|
||||
ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
|
||||
StaticExtrinsic,
|
||||
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions, StaticExtrinsic,
|
||||
};
|
||||
|
||||
/// The body of a block.
|
||||
@@ -38,7 +36,7 @@ where
|
||||
extrinsics: Vec<Vec<u8>>,
|
||||
cached_events: CachedEvents<T>,
|
||||
hash: HashFor<T>,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self, ExtrinsicDecodeErrorAt> {
|
||||
let inner = CoreExtrinsics::decode_from(extrinsics, client.metadata())?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
@@ -81,10 +79,10 @@ where
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<FoundExtrinsic<T, C, E>, Error>> {
|
||||
) -> impl Iterator<Item = Result<FoundExtrinsic<T, C, E>, ExtrinsicError>> {
|
||||
self.inner.find::<E>().map(|res| {
|
||||
match res {
|
||||
Err(e) => Err(Error::from(e)),
|
||||
Err(e) => Err(ExtrinsicError::from(e)),
|
||||
Ok(ext) => {
|
||||
// Wrap details from subxt-core into what we want here:
|
||||
let details = ExtrinsicDetails::new(
|
||||
@@ -105,18 +103,22 @@ where
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the first extrinsic found which decodes to the provided `E` type.
|
||||
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, C, E>>, Error> {
|
||||
pub fn find_first<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> Result<Option<FoundExtrinsic<T, C, E>>, ExtrinsicError> {
|
||||
self.find::<E>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, C, E>>, Error> {
|
||||
pub fn find_last<E: StaticExtrinsic>(
|
||||
&self,
|
||||
) -> Result<Option<FoundExtrinsic<T, C, E>>, ExtrinsicError> {
|
||||
self.find::<E>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, ExtrinsicError> {
|
||||
Ok(self.find::<E>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
@@ -207,38 +209,33 @@ where
|
||||
self.inner.pallet_index()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::variant_index()`].
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
self.inner.variant_index()
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::call_index()`].
|
||||
pub fn call_index(&self) -> u8 {
|
||||
self.inner.call_index()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::pallet_name()`].
|
||||
pub fn pallet_name(&self) -> Result<&str, Error> {
|
||||
self.inner.pallet_name().map_err(Into::into)
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.inner.pallet_name()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::variant_name()`].
|
||||
pub fn variant_name(&self) -> Result<&str, Error> {
|
||||
self.inner.variant_name().map_err(Into::into)
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::call_name()`].
|
||||
pub fn call_name(&self) -> &str {
|
||||
self.inner.call_name()
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::extrinsic_metadata()`].
|
||||
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
|
||||
self.inner.extrinsic_metadata().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::field_values()`].
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
self.inner.field_values().map_err(Into::into)
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::decode_as_fields()`].
|
||||
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
|
||||
self.inner.decode_as_fields().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::as_extrinsic()`].
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, ExtrinsicError> {
|
||||
self.inner.as_extrinsic::<E>().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// See [`subxt_core::blocks::ExtrinsicDetails::as_root_extrinsic()`].
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
|
||||
self.inner.as_root_extrinsic::<E>().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -249,7 +246,7 @@ where
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// The events associated with the extrinsic.
|
||||
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
|
||||
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, EventsError> {
|
||||
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
|
||||
let ext_hash = self.inner.hash();
|
||||
Ok(ExtrinsicEvents::new(ext_hash, self.index(), events))
|
||||
@@ -308,7 +305,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::iter()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, Error>> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, EventsError>> {
|
||||
self.events.iter().filter(|ev| {
|
||||
ev.as_ref()
|
||||
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
|
||||
@@ -320,7 +317,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find()`] does, with the
|
||||
/// exception that it filters out events not related to the submitted extrinsic.
|
||||
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
|
||||
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
|
||||
self.iter()
|
||||
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
|
||||
}
|
||||
@@ -330,7 +327,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_first()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.find::<Ev>().next().transpose()
|
||||
}
|
||||
|
||||
@@ -339,7 +336,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::find_last()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.find::<Ev>().last().transpose()
|
||||
}
|
||||
|
||||
@@ -347,7 +344,7 @@ impl<T: Config> ExtrinsicEvents<T> {
|
||||
///
|
||||
/// This works in the same way that [`events::Events::has()`] does, with the
|
||||
/// exception that it ignores events not related to the submitted extrinsic.
|
||||
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
|
||||
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, EventsError> {
|
||||
Ok(self.find::<Ev>().next().transpose()?.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
//! - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()),
|
||||
//! [best](crate::blocks::BlocksClient::subscribe_best()) or
|
||||
//! [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced.
|
||||
//! Prefer to subscribe to finalized blocks unless you know what you're doing.
|
||||
//! **Prefer to subscribe to finalized blocks unless you know what you're doing.**
|
||||
//!
|
||||
//! In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various
|
||||
//! information about the block, such a the [header](crate::blocks::Block::header()), [block
|
||||
//! number](crate::blocks::Block::number()) and [body (the extrinsics)](crate::blocks::Block::extrinsics()).
|
||||
//! information about the block, such a the [header](crate::blocks::Block::header()),
|
||||
//! [block number](crate::blocks::Block::number()) and [body (the extrinsics)](crate::blocks::Block::extrinsics()).
|
||||
//! [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the
|
||||
//! given block:
|
||||
//!
|
||||
@@ -29,8 +29,8 @@
|
||||
//!
|
||||
//! ## Decoding Extrinsics
|
||||
//!
|
||||
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and [iterate over
|
||||
//! the extrinsics](crate::blocks::Extrinsics::iter()) stored within it. The extrinsics yielded are of type
|
||||
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and
|
||||
//! [iterate over the extrinsics](crate::blocks::Extrinsics::iter) stored within it. The extrinsics yielded are of type
|
||||
//! [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), which is just a blob of bytes that also stores which
|
||||
//! pallet and call in that pallet it belongs to. It also contains information about signed extensions that
|
||||
//! have been used for submitting this extrinsic.
|
||||
@@ -64,8 +64,8 @@
|
||||
//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last).
|
||||
//!
|
||||
//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network.
|
||||
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce())
|
||||
//! transaction extensions.
|
||||
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and
|
||||
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) transaction extensions.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/block_decoding_static.rs")]
|
||||
@@ -73,11 +73,13 @@
|
||||
//!
|
||||
//! ### Dynamically decode the extrinsic
|
||||
//!
|
||||
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated
|
||||
//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()),
|
||||
//! which gives you access to it's fields as a [scale value composite](scale_value::Composite).
|
||||
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and transaction extensions dynamically.
|
||||
//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
|
||||
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not
|
||||
//! have access to a statically generated interface module that contains the relevant Rust types. You can
|
||||
//! [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::decode_as_fields()), which gives
|
||||
//! you access to it's fields as a [scale value composite](scale_value::Composite). The following example
|
||||
//! looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data
|
||||
//! fields and transaction extensions dynamically. Notice how we do not need to use code generation via the
|
||||
//! subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
|
||||
//! Other than that it works in a chain-agnostic way:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
@@ -90,10 +92,12 @@
|
||||
//! The [Config](crate::Config) implementation for your chain defines which transaction extensions you expect.
|
||||
//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in,
|
||||
//! you can try to [get its transaction extensions](crate::blocks::ExtrinsicDetails::transaction_extensions()).
|
||||
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find),
|
||||
//! in the returned [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
|
||||
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to
|
||||
//! [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find), in the returned
|
||||
//! [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
|
||||
//!
|
||||
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and the
|
||||
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::tip()) associated with an extrinsic, given its transaction extensions.
|
||||
//! If you prefer to do things dynamically you can get the data of the transaction extension as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
|
||||
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and
|
||||
//! the [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) associated with an extrinsic, given
|
||||
//! its transaction extensions. If you prefer to do things dynamically you can get the data of the transaction extension
|
||||
//! as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
|
||||
//!
|
||||
|
||||
@@ -22,23 +22,22 @@
|
||||
//! let constant_query = polkadot::constants().system().block_length();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a constant query:
|
||||
//! Alternately, we can dynamically construct a constant query. A dynamic query needs the return
|
||||
//! type to be specified, where we can use [`crate::dynamic::Value`] if unsure:
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let storage_query = subxt::dynamic::constant("System", "BlockLength");
|
||||
//! let storage_query = subxt::dynamic::constant::<Value>("System", "BlockLength");
|
||||
//! ```
|
||||
//!
|
||||
//! Static queries also have a static return type, so the constant is decoded appropriately. In
|
||||
//! addition, they are validated at runtime to ensure that they align with the current node state.
|
||||
//! Dynamic queries must be decoded into some static type manually, or into the dynamic
|
||||
//! [`crate::dynamic::Value`] type.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Constant queries are handed to Subxt via [`crate::constants::ConstantsClient::at()`]. It's worth
|
||||
//! noting that constant values are pulled directly out of the node metadata which Subxt has
|
||||
//! Call [`crate::constants::ConstantsClient::at()`] to return and decode the constant into the
|
||||
//! type given by the address, or [`crate::constants::ConstantsClient::bytes_at()`] to return the
|
||||
//! raw bytes for some constant.
|
||||
//!
|
||||
//! Constant values are pulled directly out of the node metadata which Subxt has
|
||||
//! already acquired, and so this function requires no network access and is available from a
|
||||
//! [`crate::OfflineClient`].
|
||||
//!
|
||||
|
||||
@@ -13,13 +13,32 @@
|
||||
//!
|
||||
//! ## Getting a custom value
|
||||
//!
|
||||
//! Custom values can be accessed via a [`CustomValuesClient`](crate::custom_values::CustomValuesClient).
|
||||
//! The client exposes an `at` function by which a custom value can be fetched, given an address to this custom value.
|
||||
//! An address can be as simple as the aforementioned __name__ as a [str]. This will return a dynamic value, that you can manually decode into the type you want.
|
||||
//! Suppose, the custom types contain a value of type `Foo` under the name `"foo"` you can access it like in this example:
|
||||
//! First, you must construct an address to access a custom value. This can be either:
|
||||
//! - a raw [`str`] which assumes the return type to be the dynamic [`crate::dynamic::Value`] type,
|
||||
//! - created via [`dynamic`](crate::custom_values::dynamic) function whereby you set the return type
|
||||
//! that you want back,
|
||||
//! - created via statically generated addresses as part of the `#[subxt]` macro which define the return type.
|
||||
//!
|
||||
//! With an address, use [`at`](crate::custom_values::CustomValuesClient::at) to access and decode specific values, and
|
||||
//! [`bytes_at`](crate::custom_values::CustomValuesClient::bytes_at) to access the raw bytes.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Dynamically accessing a custom value using a [`str`] to select which one:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use subxt::{OnlineClient, PolkadotConfig, ext::{codec::Decode, scale_decode::DecodeAsType}};
|
||||
//! use subxt::{OnlineClient, PolkadotConfig, ext::scale_decode::DecodeAsType};
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
//! let custom_value_client = api.custom_values();
|
||||
//! let foo: Value = custom_value_client.at("foo")?;
|
||||
//! ```
|
||||
//!
|
||||
//! Use the [`dynamic`](crate::custom_values::dynamic) function to select the return type:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use subxt::{OnlineClient, PolkadotConfig, ext::scale_decode::DecodeAsType};
|
||||
//!
|
||||
//! #[derive(Decode, DecodeAsType, Debug)]
|
||||
//! struct Foo {
|
||||
@@ -29,9 +48,8 @@
|
||||
//!
|
||||
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
//! let custom_value_client = api.custom_values();
|
||||
//! let foo_dynamic = custom_value_client.at("foo")?;
|
||||
//! let foo: Foo = foo_dynamic.as_type()?;
|
||||
//!
|
||||
//! let custom_value_addr = subxt::custom_values::dynamic::<Foo>("foo");
|
||||
//! let foo: Foo = custom_value_client.at(&custom_value_addr)?;
|
||||
//! ```
|
||||
//!
|
||||
//! Alternatively we also provide a statically generated api for custom values:
|
||||
@@ -49,6 +67,3 @@
|
||||
//! let foo = custom_value_client.at(&static_address)?;
|
||||
//! ```
|
||||
//!
|
||||
//! Note: Names of custom values are converted to __snake_case__ to produce a valid function name during code generation.
|
||||
//! If there are multiple values where the names would be equal when converted to __snake_case__, functions might not be statically generated for some of them, because of naming conflicts.
|
||||
//! Make sure names in the custom values of your metadata differ significantly.
|
||||
|
||||
@@ -28,15 +28,17 @@
|
||||
//! let runtime_call = polkadot::apis().metadata().metadata_versions();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a runtime call:
|
||||
//! Alternately, we can dynamically construct a runtime call. The input type can be a tuple or
|
||||
//! vec or valid types implementing [`scale_encode::EncodeAsType`], and the output can be anything
|
||||
//! implementing [`scale_decode::DecodeAsType`]:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let runtime_call = subxt::dynamic::runtime_api_call(
|
||||
//! let runtime_call = subxt::dynamic::runtime_api_call::<(), Vec<u32>>(
|
||||
//! "Metadata",
|
||||
//! "metadata_versions",
|
||||
//! Vec::<Value<()>>::new()
|
||||
//! ()
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
|
||||
@@ -9,90 +9,57 @@
|
||||
//! node storage. With Subxt, you can query this key/value storage with the following steps:
|
||||
//!
|
||||
//! 1. [Constructing a storage query](#constructing-a-storage-query).
|
||||
//! 2. [Submitting the query to get back the associated values](#submitting-it).
|
||||
//! 2. [Submitting the query to get back the associated entry](#submitting-it).
|
||||
//! 3. [Fetching](#fetching-storage-entries) or [iterating](#iterating-storage-entries) over that
|
||||
//! entry to retrieve the value or values within it.
|
||||
//!
|
||||
//! ## Constructing a storage query
|
||||
//!
|
||||
//! We can use the statically generated interface to build storage queries:
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let account = dev::alice().public_key().into();
|
||||
//! let storage_query = polkadot::storage().system().account(account);
|
||||
//! let storage_query = polkadot::storage().system().account();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a storage query. This will not be type checked or
|
||||
//! validated until it's submitted:
|
||||
//! Alternately, we can dynamically construct a storage query. A dynamic query needs the input
|
||||
//! and return value types to be specified, where we can use [`crate::dynamic::Value`] if unsure.
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let account = dev::alice().public_key();
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
|
||||
//! Value::from_bytes(account)
|
||||
//! ]);
|
||||
//! let storage_query = subxt::dynamic::storage::<(Value,), Value>("System", "Account");
|
||||
//! ```
|
||||
//!
|
||||
//! As well as accessing specific entries, some storage locations can also be iterated over (such as
|
||||
//! the map of account information). To do this, suffix `_iter` onto the query constructor (this
|
||||
//! will only be available on static constructors when iteration is actually possible):
|
||||
//!
|
||||
//! ```rust,no_run,standalone_crate
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! // A static query capable of iterating over accounts:
|
||||
//! let storage_query = polkadot::storage().system().account_iter();
|
||||
//! // A dynamic query to do the same:
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", ());
|
||||
//! ```
|
||||
//!
|
||||
//! Some storage entries are maps with multiple keys. As an example, we might end up with
|
||||
//! an API like `runtime::storage().foo().bar(u8, bool, u16, String)` to fetch some entry "bar".
|
||||
//! When this is the case, the codegen will generate multiple iterator query functions alongside
|
||||
//! the function to fetch an individual value:
|
||||
//!
|
||||
//! - `runtime::storage().foo().bar(u8, bool, u16, String)`: fetch a single entry from the "bar" map.
|
||||
//! - `runtime::storage().foo().bar_iter()`: iterate over all of the entries in the "bar" map.
|
||||
//! - `runtime::storage().foo().bar_iter1(u8)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8`.
|
||||
//! - `runtime::storage().foo().bar_iter2(u8, bool)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8` and `bool` value.
|
||||
//! - `runtime::storage().foo().bar_iter3(u8, bool, u16)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8`, `bool` and `u16` value.
|
||||
//!
|
||||
//! All valid storage queries implement [`crate::storage::Address`]. As well as describing
|
||||
//! how to build a valid storage query, this trait also has some associated types that determine the
|
||||
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
|
||||
//! over storage entries using it).
|
||||
//!
|
||||
//! Static queries set appropriate values for these associated types, and can therefore only be used
|
||||
//! where it makes sense. Dynamic queries don't know any better and can be used in more places, but
|
||||
//! may fail at runtime instead if they are invalid in those places.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to
|
||||
//! Storage queries can be handed to various functions in [`crate::storage::StorageClientAt`] in order to
|
||||
//! obtain the associated values (also referred to as storage entries) back.
|
||||
//!
|
||||
//! The core API here is [`crate::storage::StorageClientAt::entry()`], which takes a query and looks up the
|
||||
//! corresponding storage entry, from which you can then fetch or iterate over the values contained within.
|
||||
//! [`crate::storage::StorageClientAt::fetch()`] and [`crate::storage::StorageClientAt::iter()`] are shorthand
|
||||
//! for this.
|
||||
//!
|
||||
//! When you wish to manually query some entry, [`crate::storage::StorageClientAt::fetch_raw()`] exists to take
|
||||
//! in raw bytes pointing at some storage value, and return the value bytes if possible. [`crate::storage::StorageClientAt::storage_version()`]
|
||||
//! and [`crate::storage::StorageClientAt::runtime_wasm_code()`] use this to retrieve the version of some storage API
|
||||
//! and the current Runtime WASM blob respectively.
|
||||
//!
|
||||
//! ### Fetching storage entries
|
||||
//!
|
||||
//! The simplest way to access storage entries is to construct a query and then call either
|
||||
//! [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the
|
||||
//! latter will only work for storage queries that have a default value when empty):
|
||||
//! [`crate::storage::StorageClientAt::fetch()`]:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/storage_fetch.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! For completeness, below is an example using a dynamic query instead. The return type from a
|
||||
//! dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a
|
||||
//! [`crate::dynamic::Value`], or else the raw bytes can be accessed instead.
|
||||
//! For completeness, below is an example using a dynamic query instead. Dynamic queries can define the types that
|
||||
//! they wish to accept inputs and decode the return value into ([`crate::dynamic::Value`] can be used here anywhere we
|
||||
//! are not sure of the specific types).
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/storage_fetch_dynamic.rs")]
|
||||
@@ -113,18 +80,3 @@
|
||||
#![doc = include_str!("../../../examples/storage_iterating_dynamic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! Here is an example of iterating over partial keys. In this example some multi-signature operations
|
||||
//! are sent to the node. We can iterate over the pending multisig operations of a single multisig account:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/storage_iterating_partial.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Advanced
|
||||
//!
|
||||
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
|
||||
//! [`crate::storage::Storage::fetch_raw_keys`]. Both of these take raw bytes as arguments, which can be
|
||||
//! obtained from a [`crate::storage::Address`] by using
|
||||
//! [`crate::storage::StorageClient::address_bytes()`] or
|
||||
//! [`crate::storage::StorageClient::address_root_bytes()`].
|
||||
//!
|
||||
|
||||
@@ -13,6 +13,6 @@ mod online_client;
|
||||
|
||||
pub use offline_client::{OfflineClient, OfflineClientT};
|
||||
pub use online_client::{
|
||||
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError,
|
||||
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update,
|
||||
};
|
||||
pub use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
blocks::{BlockRef, BlocksClient},
|
||||
config::{Config, HashFor},
|
||||
constants::ConstantsClient,
|
||||
error::Error,
|
||||
error::{BackendError, OnlineClientError, RuntimeUpdateeApplyError, RuntimeUpdaterError},
|
||||
events::EventsClient,
|
||||
runtime_api::RuntimeApiClient,
|
||||
storage::StorageClient,
|
||||
@@ -18,6 +18,7 @@ use crate::{
|
||||
view_functions::ViewFunctionsClient,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use futures::TryFutureExt;
|
||||
use futures::future;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
@@ -60,13 +61,13 @@ impl<T: Config> std::fmt::Debug for OnlineClient<T> {
|
||||
impl<T: Config> OnlineClient<T> {
|
||||
/// Construct a new [`OnlineClient`] using default settings which
|
||||
/// point to a locally running node on `ws://127.0.0.1:9944`.
|
||||
pub async fn new() -> Result<OnlineClient<T>, Error> {
|
||||
pub async fn new() -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let url = "ws://127.0.0.1:9944";
|
||||
OnlineClient::from_url(url).await
|
||||
}
|
||||
|
||||
/// Construct a new [`OnlineClient`], providing a URL to connect to.
|
||||
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
|
||||
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
subxt_rpcs::utils::validate_url_is_secure(url.as_ref())?;
|
||||
OnlineClient::from_insecure_url(url).await
|
||||
}
|
||||
@@ -74,7 +75,9 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// Construct a new [`OnlineClient`], providing a URL to connect to.
|
||||
///
|
||||
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
|
||||
pub async fn from_insecure_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
|
||||
pub async fn from_insecure_url(
|
||||
url: impl AsRef<str>,
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let client = RpcClient::from_insecure_url(url).await?;
|
||||
let backend = LegacyBackend::builder().build(client);
|
||||
OnlineClient::from_backend(Arc::new(backend)).await
|
||||
@@ -86,7 +89,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// This will use the current default [`Backend`], which may change in future releases.
|
||||
pub async fn from_rpc_client(
|
||||
rpc_client: impl Into<RpcClient>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let rpc_client = rpc_client.into();
|
||||
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
|
||||
OnlineClient::from_backend(backend).await
|
||||
@@ -110,7 +113,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: impl Into<Metadata>,
|
||||
rpc_client: impl Into<RpcClient>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let rpc_client = rpc_client.into();
|
||||
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
|
||||
OnlineClient::from_backend_with(genesis_hash, runtime_version, metadata, backend)
|
||||
@@ -118,13 +121,23 @@ impl<T: Config> OnlineClient<T> {
|
||||
|
||||
/// Construct a new [`OnlineClient`] by providing an underlying [`Backend`]
|
||||
/// implementation to power it. Other details will be obtained from the chain.
|
||||
pub async fn from_backend<B: Backend<T>>(backend: Arc<B>) -> Result<OnlineClient<T>, Error> {
|
||||
let latest_block = backend.latest_finalized_block_ref().await?;
|
||||
pub async fn from_backend<B: Backend<T>>(
|
||||
backend: Arc<B>,
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
let latest_block = backend
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(OnlineClientError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
let (genesis_hash, runtime_version, metadata) = future::join3(
|
||||
backend.genesis_hash(),
|
||||
backend.current_runtime_version(),
|
||||
OnlineClient::fetch_metadata(&*backend, latest_block.hash()),
|
||||
backend
|
||||
.genesis_hash()
|
||||
.map_err(OnlineClientError::CannotGetGenesisHash),
|
||||
backend
|
||||
.current_runtime_version()
|
||||
.map_err(OnlineClientError::CannotGetCurrentRuntimeVersion),
|
||||
OnlineClient::fetch_metadata(&*backend, latest_block.hash())
|
||||
.map_err(OnlineClientError::CannotFetchMetadata),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -148,7 +161,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: impl Into<Metadata>,
|
||||
backend: Arc<B>,
|
||||
) -> Result<OnlineClient<T>, Error> {
|
||||
) -> Result<OnlineClient<T>, OnlineClientError> {
|
||||
use subxt_core::config::Hasher;
|
||||
|
||||
let metadata = metadata.into();
|
||||
@@ -169,7 +182,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
async fn fetch_metadata(
|
||||
backend: &dyn Backend<T>,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<Metadata, Error> {
|
||||
) -> Result<Metadata, BackendError> {
|
||||
#[cfg(feature = "unstable-metadata")]
|
||||
{
|
||||
/// The unstable metadata version number.
|
||||
@@ -194,7 +207,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
async fn fetch_latest_stable_metadata(
|
||||
backend: &dyn Backend<T>,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<Metadata, Error> {
|
||||
) -> Result<Metadata, BackendError> {
|
||||
// The metadata versions we support in Subxt, from newest to oldest.
|
||||
use subxt_metadata::SUPPORTED_METADATA_VERSIONS;
|
||||
|
||||
@@ -235,7 +248,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// tokio::spawn(async move {
|
||||
/// let mut update_stream = updater.runtime_updates().await.unwrap();
|
||||
///
|
||||
/// while let Some(Ok(update)) = update_stream.next().await {
|
||||
/// while let Ok(update) = update_stream.next().await {
|
||||
/// let version = update.runtime_version().spec_version;
|
||||
///
|
||||
/// match updater.apply_update(update) {
|
||||
@@ -416,9 +429,9 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
}
|
||||
|
||||
/// Tries to apply a new update.
|
||||
pub fn apply_update(&self, update: Update) -> Result<(), UpgradeError> {
|
||||
pub fn apply_update(&self, update: Update) -> Result<(), RuntimeUpdateeApplyError> {
|
||||
if !self.is_runtime_version_different(&update.runtime_version) {
|
||||
return Err(UpgradeError::SameVersion);
|
||||
return Err(RuntimeUpdateeApplyError::SameVersion);
|
||||
}
|
||||
|
||||
self.do_update(update);
|
||||
@@ -430,12 +443,12 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
///
|
||||
/// *Note:* This will run indefinitely until it errors, so the typical usage
|
||||
/// would be to run it in a separate background task.
|
||||
pub async fn perform_runtime_updates(&self) -> Result<(), Error> {
|
||||
pub async fn perform_runtime_updates(&self) -> Result<(), RuntimeUpdaterError> {
|
||||
// Obtain an update subscription to further detect changes in the runtime version of the node.
|
||||
let mut runtime_version_stream = self.runtime_updates().await?;
|
||||
|
||||
while let Some(update) = runtime_version_stream.next().await {
|
||||
let update = update?;
|
||||
loop {
|
||||
let update = runtime_version_stream.next().await?;
|
||||
|
||||
// This only fails if received the runtime version is the same the current runtime version
|
||||
// which might occur because that runtime subscriptions in substrate sends out the initial
|
||||
@@ -443,8 +456,6 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
// Thus, fine to ignore here as it strictly speaking isn't really an error
|
||||
let _ = self.apply_update(update);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Low-level API to get runtime updates as a stream but it's doesn't check if the
|
||||
@@ -452,9 +463,16 @@ impl<T: Config> ClientRuntimeUpdater<T> {
|
||||
///
|
||||
/// Instead that's up to the user of this API to decide when to update and
|
||||
/// to perform the actual updating.
|
||||
pub async fn runtime_updates(&self) -> Result<RuntimeUpdaterStream<T>, Error> {
|
||||
pub async fn runtime_updates(&self) -> Result<RuntimeUpdaterStream<T>, RuntimeUpdaterError> {
|
||||
let stream = self
|
||||
.0
|
||||
.backend()
|
||||
.stream_runtime_version()
|
||||
.await
|
||||
.map_err(RuntimeUpdaterError::CannotStreamRuntimeVersion)?;
|
||||
|
||||
Ok(RuntimeUpdaterStream {
|
||||
stream: self.0.backend().stream_runtime_version().await?,
|
||||
stream,
|
||||
client: self.0.clone(),
|
||||
})
|
||||
}
|
||||
@@ -468,38 +486,27 @@ pub struct RuntimeUpdaterStream<T: Config> {
|
||||
|
||||
impl<T: Config> RuntimeUpdaterStream<T> {
|
||||
/// Wait for the next runtime update.
|
||||
pub async fn next(&mut self) -> Option<Result<Update, Error>> {
|
||||
let runtime_version = match self.stream.next().await? {
|
||||
Ok(runtime_version) => runtime_version,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
pub async fn next(&mut self) -> Result<Update, RuntimeUpdaterError> {
|
||||
let runtime_version = self
|
||||
.stream
|
||||
.next()
|
||||
.await
|
||||
.ok_or(RuntimeUpdaterError::UnexpectedEndOfUpdateStream)?
|
||||
.map_err(RuntimeUpdaterError::CannotGetNextRuntimeVersion)?;
|
||||
|
||||
let at =
|
||||
match wait_runtime_upgrade_in_finalized_block(&self.client, &runtime_version).await? {
|
||||
Ok(at) => at,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let at = wait_runtime_upgrade_in_finalized_block(&self.client, &runtime_version).await?;
|
||||
|
||||
let metadata = match OnlineClient::fetch_metadata(self.client.backend(), at.hash()).await {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let metadata = OnlineClient::fetch_metadata(self.client.backend(), at.hash())
|
||||
.await
|
||||
.map_err(RuntimeUpdaterError::CannotFetchNewMetadata)?;
|
||||
|
||||
Some(Ok(Update {
|
||||
Ok(Update {
|
||||
metadata,
|
||||
runtime_version,
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can occur during upgrade.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UpgradeError {
|
||||
/// The version is the same as the current version.
|
||||
SameVersion,
|
||||
}
|
||||
|
||||
/// Represents the state when a runtime upgrade occurred.
|
||||
pub struct Update {
|
||||
runtime_version: RuntimeVersion,
|
||||
@@ -522,64 +529,52 @@ impl Update {
|
||||
async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
|
||||
client: &OnlineClient<T>,
|
||||
runtime_version: &RuntimeVersion,
|
||||
) -> Option<Result<BlockRef<HashFor<T>>, Error>> {
|
||||
use scale_value::At;
|
||||
|
||||
) -> Result<BlockRef<HashFor<T>>, RuntimeUpdaterError> {
|
||||
let hasher = client
|
||||
.inner
|
||||
.read()
|
||||
.expect("Lock shouldn't be poisoned")
|
||||
.hasher;
|
||||
|
||||
let mut block_sub = match client
|
||||
let mut block_sub = client
|
||||
.backend()
|
||||
.stream_finalized_block_headers(hasher)
|
||||
.await
|
||||
{
|
||||
Ok(s) => s,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
.map_err(RuntimeUpdaterError::CannotStreamFinalizedBlocks)?;
|
||||
|
||||
let block_ref = loop {
|
||||
let (_, block_ref) = match block_sub.next().await? {
|
||||
Ok(n) => n,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
let (_, block_ref) = block_sub
|
||||
.next()
|
||||
.await
|
||||
.ok_or(RuntimeUpdaterError::UnexpectedEndOfBlockStream)?
|
||||
.map_err(RuntimeUpdaterError::CannotGetNextFinalizedBlock)?;
|
||||
|
||||
let key: Vec<scale_value::Value> = vec![];
|
||||
let addr = crate::dynamic::storage("System", "LastRuntimeUpgrade", key);
|
||||
let addr =
|
||||
crate::dynamic::storage::<(), scale_value::Value>("System", "LastRuntimeUpgrade");
|
||||
|
||||
let chunk = match client.storage().at(block_ref.hash()).fetch(&addr).await {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => {
|
||||
// The storage `system::lastRuntimeUpgrade` should always exist.
|
||||
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
|
||||
unreachable!("The storage item `system::lastRuntimeUpgrade` should always exist")
|
||||
}
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
let client_at = client.storage().at(block_ref.hash());
|
||||
let value = client_at
|
||||
.entry(addr)
|
||||
// The storage `system::lastRuntimeUpgrade` should always exist.
|
||||
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
|
||||
.map_err(|_| RuntimeUpdaterError::CantFindSystemLastRuntimeUpgrade)?
|
||||
.fetch(())
|
||||
.await
|
||||
.map_err(RuntimeUpdaterError::CantFetchLastRuntimeUpgrade)?
|
||||
.decode_as::<LastRuntimeUpgrade>()
|
||||
.map_err(RuntimeUpdaterError::CannotDecodeLastRuntimeUpgrade)?;
|
||||
|
||||
let scale_val = match chunk.to_value() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
|
||||
let Some(Ok(spec_version)) = scale_val
|
||||
.at("spec_version")
|
||||
.and_then(|v| v.as_u128())
|
||||
.map(u32::try_from)
|
||||
else {
|
||||
return Some(Err(Error::Other(
|
||||
"Decoding `RuntimeVersion::spec_version` as u32 failed".to_string(),
|
||||
)));
|
||||
};
|
||||
#[derive(scale_decode::DecodeAsType)]
|
||||
struct LastRuntimeUpgrade {
|
||||
spec_version: u32,
|
||||
}
|
||||
|
||||
// We are waiting for the chain to have the same spec version
|
||||
// as sent out via the runtime subscription.
|
||||
if spec_version == runtime_version.spec_version {
|
||||
if value.spec_version == runtime_version.spec_version {
|
||||
break block_ref;
|
||||
}
|
||||
};
|
||||
|
||||
Some(Ok(block_ref))
|
||||
Ok(block_ref)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{Config, client::OfflineClientT, error::Error};
|
||||
use crate::{Config, client::OfflineClientT, error::ConstantError};
|
||||
use derive_where::derive_where;
|
||||
use subxt_core::constants::address::Address;
|
||||
|
||||
@@ -28,16 +28,22 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or constant in question do not exist at all).
|
||||
pub fn validate<Addr: Address>(&self, address: &Addr) -> Result<(), Error> {
|
||||
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), ConstantError> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::validate(address, &metadata).map_err(Error::from)
|
||||
subxt_core::constants::validate(address, &metadata)
|
||||
}
|
||||
|
||||
/// Access the constant at the address given, returning the type defined by this address.
|
||||
/// This is probably used with addresses given from static codegen, although you can manually
|
||||
/// construct your own, too.
|
||||
pub fn at<Addr: Address>(&self, address: &Addr) -> Result<Addr::Target, Error> {
|
||||
pub fn at<Addr: Address>(&self, address: Addr) -> Result<Addr::Target, ConstantError> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::get(address, &metadata).map_err(Error::from)
|
||||
subxt_core::constants::get(address, &metadata)
|
||||
}
|
||||
|
||||
/// Access the bytes of a constant by the address it is registered under.
|
||||
pub fn bytes_at<Addr: Address>(&self, address: Addr) -> Result<Vec<u8>, ConstantError> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::get_bytes(address, &metadata)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,4 @@
|
||||
mod constants_client;
|
||||
|
||||
pub use constants_client::ConstantsClient;
|
||||
pub use subxt_core::constants::address::{
|
||||
Address, DefaultAddress, DynamicAddress, StaticAddress, dynamic,
|
||||
};
|
||||
pub use subxt_core::constants::address::{Address, DynamicAddress, StaticAddress, dynamic};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::client::OfflineClientT;
|
||||
use crate::{Config, Error};
|
||||
use crate::{Config, error::CustomValueError};
|
||||
use derive_where::derive_where;
|
||||
|
||||
use subxt_core::custom_values::address::{Address, Yes};
|
||||
use subxt_core::custom_values::address::{Address, Maybe};
|
||||
|
||||
/// A client for accessing custom values stored in the metadata.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -24,29 +24,29 @@ impl<T, Client> CustomValuesClient<T, Client> {
|
||||
impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
|
||||
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
|
||||
/// or a static address from the generated static interface to get a value of a static type returned.
|
||||
pub fn at<Addr: Address<IsDecodable = Yes> + ?Sized>(
|
||||
pub fn at<Addr: Address<IsDecodable = Maybe>>(
|
||||
&self,
|
||||
address: &Addr,
|
||||
) -> Result<Addr::Target, Error> {
|
||||
subxt_core::custom_values::get(address, &self.client.metadata()).map_err(Into::into)
|
||||
address: Addr,
|
||||
) -> Result<Addr::Target, CustomValueError> {
|
||||
subxt_core::custom_values::get(address, &self.client.metadata())
|
||||
}
|
||||
|
||||
/// Access the bytes of a custom value by the address it is registered under.
|
||||
pub fn bytes_at<Addr: Address + ?Sized>(&self, address: &Addr) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::custom_values::get_bytes(address, &self.client.metadata()).map_err(Into::into)
|
||||
pub fn bytes_at<Addr: Address>(&self, address: Addr) -> Result<Vec<u8>, CustomValueError> {
|
||||
subxt_core::custom_values::get_bytes(address, &self.client.metadata())
|
||||
}
|
||||
|
||||
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
|
||||
pub fn validate<Addr: Address + ?Sized>(&self, address: &Addr) -> Result<(), Error> {
|
||||
subxt_core::custom_values::validate(address, &self.client.metadata()).map_err(Into::into)
|
||||
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), CustomValueError> {
|
||||
subxt_core::custom_values::validate(address, &self.client.metadata())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::custom_values::CustomValuesClient;
|
||||
use crate::custom_values::{self, CustomValuesClient};
|
||||
use crate::{Metadata, OfflineClient, SubstrateConfig};
|
||||
use codec::Encode;
|
||||
use scale_decode::DecodeAsType;
|
||||
@@ -104,7 +104,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
|
||||
Metadata::from(metadata)
|
||||
metadata
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -117,10 +117,12 @@ mod tests {
|
||||
},
|
||||
mock_metadata(),
|
||||
);
|
||||
|
||||
let custom_value_client = CustomValuesClient::new(client);
|
||||
assert!(custom_value_client.at("No one").is_err());
|
||||
let person_decoded_value_thunk = custom_value_client.at("Person").unwrap();
|
||||
let person: Person = person_decoded_value_thunk.as_type().unwrap();
|
||||
|
||||
let person_addr = custom_values::dynamic::<Person>("Person");
|
||||
let person = custom_value_client.at(&person_addr).unwrap();
|
||||
assert_eq!(
|
||||
person,
|
||||
Person {
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
mod custom_values_client;
|
||||
|
||||
pub use custom_values_client::CustomValuesClient;
|
||||
pub use subxt_core::custom_values::address::{Address, StaticAddress, Yes};
|
||||
pub use subxt_core::custom_values::address::{Address, DynamicAddress, StaticAddress, dynamic};
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
//! A representation of the dispatch error; an error returned when
|
||||
//! something fails in trying to submit/execute a transaction.
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use super::{DispatchErrorDecodeError, ModuleErrorDecodeError, ModuleErrorDetailsError};
|
||||
use crate::metadata::Metadata;
|
||||
use core::fmt::Debug;
|
||||
use scale_decode::{DecodeAsType, TypeResolver, visitor::DecodeAsTypeResult};
|
||||
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
||||
use super::{Error, MetadataError};
|
||||
|
||||
/// An error dispatching a transaction.
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[non_exhaustive]
|
||||
pub enum DispatchError {
|
||||
/// Some error occurred.
|
||||
@@ -169,11 +168,20 @@ impl std::fmt::Display for ModuleError {
|
||||
|
||||
impl ModuleError {
|
||||
/// Return more details about this error.
|
||||
pub fn details(&self) -> Result<ModuleErrorDetails<'_>, MetadataError> {
|
||||
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
|
||||
pub fn details(&self) -> Result<ModuleErrorDetails<'_>, ModuleErrorDetailsError> {
|
||||
let pallet = self
|
||||
.metadata
|
||||
.pallet_by_error_index(self.pallet_index())
|
||||
.ok_or(ModuleErrorDetailsError::PalletNotFound {
|
||||
pallet_index: self.pallet_index(),
|
||||
})?;
|
||||
|
||||
let variant = pallet
|
||||
.error_variant_by_index(self.error_index())
|
||||
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.error_index()))?;
|
||||
.ok_or_else(|| ModuleErrorDetailsError::ErrorVariantNotFound {
|
||||
pallet_name: pallet.name().into(),
|
||||
error_index: self.error_index(),
|
||||
})?;
|
||||
|
||||
Ok(ModuleErrorDetails { pallet, variant })
|
||||
}
|
||||
@@ -209,12 +217,13 @@ impl ModuleError {
|
||||
}
|
||||
|
||||
/// Attempts to decode the ModuleError into the top outer Error enum.
|
||||
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
pub fn as_root_error<E: DecodeAsType>(&self) -> Result<E, ModuleErrorDecodeError> {
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &self.bytes[..],
|
||||
self.metadata.outer_enums().error_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
)
|
||||
.map_err(ModuleErrorDecodeError)?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
@@ -223,7 +232,7 @@ impl ModuleError {
|
||||
/// Details about the module error.
|
||||
pub struct ModuleErrorDetails<'a> {
|
||||
/// The pallet that the error is in
|
||||
pub pallet: crate::metadata::types::PalletMetadata<'a>,
|
||||
pub pallet: subxt_metadata::PalletMetadata<'a>,
|
||||
/// The variant representing the error
|
||||
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
|
||||
}
|
||||
@@ -234,11 +243,11 @@ impl DispatchError {
|
||||
pub fn decode_from<'a>(
|
||||
bytes: impl Into<Cow<'a, [u8]>>,
|
||||
metadata: Metadata,
|
||||
) -> Result<Self, super::Error> {
|
||||
) -> Result<Self, DispatchErrorDecodeError> {
|
||||
let bytes = bytes.into();
|
||||
let dispatch_error_ty_id = metadata
|
||||
.dispatch_error_ty()
|
||||
.ok_or(MetadataError::DispatchErrorNotFound)?;
|
||||
.ok_or(DispatchErrorDecodeError::DispatchErrorTypeIdNotFound)?;
|
||||
|
||||
// The aim is to decode our bytes into roughly this shape. This is copied from
|
||||
// `sp_runtime::DispatchError`; we need the variant names and any inner variant
|
||||
@@ -290,11 +299,12 @@ impl DispatchError {
|
||||
}
|
||||
|
||||
// Decode into our temporary error:
|
||||
let decoded_dispatch_err = DecodedDispatchError::decode_with_metadata(
|
||||
let decoded_dispatch_err = DecodedDispatchError::decode_as_type(
|
||||
&mut &*bytes,
|
||||
dispatch_error_ty_id,
|
||||
&metadata,
|
||||
)?;
|
||||
metadata.types(),
|
||||
)
|
||||
.map_err(DispatchErrorDecodeError::CouldNotDecodeDispatchError)?;
|
||||
|
||||
// Convert into the outward-facing error, mainly by handling the Module variant.
|
||||
let dispatch_error = match decoded_dispatch_err {
|
||||
@@ -333,7 +343,9 @@ impl DispatchError {
|
||||
"Can't decode error sp_runtime::DispatchError: bytes do not match known shapes"
|
||||
);
|
||||
// Return _all_ of the bytes; every "unknown" return should be consistent.
|
||||
return Err(super::Error::Unknown(bytes.to_vec()));
|
||||
return Err(DispatchErrorDecodeError::CouldNotDecodeModuleError {
|
||||
bytes: bytes.to_vec(),
|
||||
});
|
||||
};
|
||||
|
||||
// And return our outward-facing version:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/// Display hex strings.
|
||||
#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
|
||||
pub struct Hex(String);
|
||||
|
||||
impl<T: AsRef<[u8]>> From<T> for Hex {
|
||||
fn from(value: T) -> Self {
|
||||
Hex(hex::encode(value.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Hex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
+607
-145
@@ -5,8 +5,7 @@
|
||||
//! Types representing the errors that can be returned.
|
||||
|
||||
mod dispatch_error;
|
||||
|
||||
use subxt_core::error::{BlockError as CoreBlockError, Error as CoreError};
|
||||
mod hex;
|
||||
|
||||
crate::macros::cfg_unstable_light_client! {
|
||||
pub use subxt_lightclient::LightClientError;
|
||||
@@ -18,94 +17,98 @@ pub use dispatch_error::{
|
||||
};
|
||||
|
||||
// Re-expose the errors we use from other crates here:
|
||||
pub use crate::metadata::Metadata;
|
||||
pub use crate::Metadata;
|
||||
pub use hex::Hex;
|
||||
pub use scale_decode::Error as DecodeError;
|
||||
pub use scale_encode::Error as EncodeError;
|
||||
pub use subxt_core::error::{ExtrinsicError, MetadataError, StorageAddressError};
|
||||
pub use subxt_metadata::TryFromError as MetadataTryFromError;
|
||||
|
||||
/// The underlying error enum, generic over the type held by the `Runtime`
|
||||
/// variant. Prefer to use the [`Error<E>`] and [`Error`] aliases over
|
||||
/// using this type directly.
|
||||
// Re-export core error types we're just reusing.
|
||||
pub use subxt_core::error::{
|
||||
ConstantError,
|
||||
CustomValueError,
|
||||
EventsError as CoreEventsError,
|
||||
// These errors are exposed as-is:
|
||||
ExtrinsicDecodeErrorAt,
|
||||
// These errors are wrapped:
|
||||
ExtrinsicError as CoreExtrinsicError,
|
||||
RuntimeApiError as CoreRuntimeApiError,
|
||||
StorageError as CoreStorageError,
|
||||
StorageKeyError,
|
||||
StorageValueError,
|
||||
ViewFunctionError as CoreViewFunctionError,
|
||||
};
|
||||
|
||||
/// A global error type. Any of the errors exposed here can convert into this
|
||||
/// error via `.into()`, but this error isn't itself exposed from anything.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Error {
|
||||
/// Io error.
|
||||
#[error("Io error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// Codec error.
|
||||
#[error("Scale codec error: {0}")]
|
||||
Codec(#[from] codec::Error),
|
||||
/// Rpc error.
|
||||
#[error(transparent)]
|
||||
Rpc(#[from] RpcError),
|
||||
/// Serde serialization error
|
||||
#[error("Serde json error: {0}")]
|
||||
Serialization(#[from] serde_json::error::Error),
|
||||
/// Error working with metadata.
|
||||
#[error("Metadata error: {0}")]
|
||||
Metadata(#[from] MetadataError),
|
||||
/// Error decoding metadata.
|
||||
#[error("Metadata Decoding error: {0}")]
|
||||
MetadataDecoding(#[from] MetadataTryFromError),
|
||||
/// Runtime error.
|
||||
#[error("Runtime error: {0}")]
|
||||
Runtime(#[from] DispatchError),
|
||||
/// Error decoding to a [`crate::dynamic::Value`].
|
||||
#[error("Error decoding into dynamic value: {0}")]
|
||||
Decode(#[from] DecodeError),
|
||||
/// Error encoding from a [`crate::dynamic::Value`].
|
||||
#[error("Error encoding from dynamic value: {0}")]
|
||||
Encode(#[from] EncodeError),
|
||||
/// Transaction progress error.
|
||||
#[error("Transaction error: {0}")]
|
||||
Transaction(#[from] TransactionError),
|
||||
/// Error constructing the appropriate extrinsic params.
|
||||
#[error("Extrinsic params error: {0}")]
|
||||
Extrinsic(#[from] ExtrinsicError),
|
||||
/// Block related error.
|
||||
#[error("Block error: {0}")]
|
||||
Block(#[from] BlockError),
|
||||
/// An error encoding a storage address.
|
||||
#[error("Error encoding storage address: {0}")]
|
||||
StorageAddress(#[from] StorageAddressError),
|
||||
/// The bytes representing an error that we were unable to decode.
|
||||
#[error("An error occurred but it could not be decoded: {0:?}")]
|
||||
Unknown(Vec<u8>),
|
||||
/// Light client error.
|
||||
ExtrinsicDecodeErrorAt(#[from] ExtrinsicDecodeErrorAt),
|
||||
#[error(transparent)]
|
||||
ConstantError(#[from] ConstantError),
|
||||
#[error(transparent)]
|
||||
CustomValueError(#[from] CustomValueError),
|
||||
#[error(transparent)]
|
||||
StorageKeyError(#[from] StorageKeyError),
|
||||
#[error(transparent)]
|
||||
StorageValueError(#[from] StorageValueError),
|
||||
#[error(transparent)]
|
||||
BackendError(#[from] BackendError),
|
||||
#[error(transparent)]
|
||||
BlockError(#[from] BlockError),
|
||||
#[error(transparent)]
|
||||
AccountNonceError(#[from] AccountNonceError),
|
||||
#[error(transparent)]
|
||||
OnlineClientError(#[from] OnlineClientError),
|
||||
#[error(transparent)]
|
||||
RuntimeUpdaterError(#[from] RuntimeUpdaterError),
|
||||
#[error(transparent)]
|
||||
RuntimeUpdateeApplyError(#[from] RuntimeUpdateeApplyError),
|
||||
#[error(transparent)]
|
||||
RuntimeApiError(#[from] RuntimeApiError),
|
||||
#[error(transparent)]
|
||||
EventsError(#[from] EventsError),
|
||||
#[error(transparent)]
|
||||
ExtrinsicError(#[from] ExtrinsicError),
|
||||
#[error(transparent)]
|
||||
ViewFunctionError(#[from] ViewFunctionError),
|
||||
#[error(transparent)]
|
||||
TransactionProgressError(#[from] TransactionProgressError),
|
||||
#[error(transparent)]
|
||||
TransactionStatusError(#[from] TransactionStatusError),
|
||||
#[error(transparent)]
|
||||
TransactionEventsError(#[from] TransactionEventsError),
|
||||
#[error(transparent)]
|
||||
TransactionFinalizedSuccessError(#[from] TransactionFinalizedSuccessError),
|
||||
#[error(transparent)]
|
||||
ModuleErrorDetailsError(#[from] ModuleErrorDetailsError),
|
||||
#[error(transparent)]
|
||||
ModuleErrorDecodeError(#[from] ModuleErrorDecodeError),
|
||||
#[error(transparent)]
|
||||
DispatchErrorDecodeError(#[from] DispatchErrorDecodeError),
|
||||
#[error(transparent)]
|
||||
StorageError(#[from] StorageError),
|
||||
// Dev note: Subxt doesn't directly return Raw* errors. These exist so that when
|
||||
// users use common crates (like parity-scale-codec and subxt-rpcs), errors returned
|
||||
// there can be handled automatically using ? when the expected error is subxt::Error.
|
||||
#[error("Other RPC client error: {0}")]
|
||||
OtherRpcClientError(#[from] subxt_rpcs::Error),
|
||||
#[error("Other codec error: {0}")]
|
||||
OtherCodecError(#[from] codec::Error),
|
||||
#[cfg(feature = "unstable-light-client")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-light-client")))]
|
||||
#[error("An error occurred but it could not be decoded: {0}")]
|
||||
LightClient(#[from] LightClientError),
|
||||
/// Other error.
|
||||
#[error("Other light client error: {0}")]
|
||||
OtherLightClientError(#[from] subxt_lightclient::LightClientError),
|
||||
#[cfg(feature = "unstable-light-client")]
|
||||
#[error("Other light client RPC error: {0}")]
|
||||
OtherLightClientRpcError(#[from] subxt_lightclient::LightClientRpcError),
|
||||
// Dev note: Nothing in subxt should ever emit this error. It can instead be used
|
||||
// to easily map other errors into a subxt::Error for convenience. Some From impls
|
||||
// make this automatic for common "other" error types.
|
||||
#[error("Other error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<CoreError> for Error {
|
||||
fn from(value: CoreError) -> Self {
|
||||
match value {
|
||||
CoreError::Codec(e) => Error::Codec(e),
|
||||
CoreError::Metadata(e) => Error::Metadata(e),
|
||||
CoreError::StorageAddress(e) => Error::StorageAddress(e),
|
||||
CoreError::Decode(e) => Error::Decode(e),
|
||||
CoreError::Encode(e) => Error::Encode(e),
|
||||
CoreError::Extrinsic(e) => Error::Extrinsic(e),
|
||||
CoreError::Block(e) => Error::Block(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Error {
|
||||
fn from(error: &'a str) -> Self {
|
||||
Error::Other(error.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(error: String) -> Self {
|
||||
Error::Other(error)
|
||||
}
|
||||
Other(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for Error {
|
||||
@@ -114,24 +117,83 @@ impl From<std::convert::Infallible> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<scale_decode::visitor::DecodeError> for Error {
|
||||
fn from(value: scale_decode::visitor::DecodeError) -> Self {
|
||||
Error::Decode(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<subxt_rpcs::Error> for Error {
|
||||
fn from(value: subxt_rpcs::Error) -> Self {
|
||||
Error::Rpc(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Create a generic error. This is a quick workaround when you are using
|
||||
/// [`Error`] and have a non-Subxt error to return.
|
||||
pub fn other<E: std::error::Error + Send + Sync + 'static>(error: E) -> Error {
|
||||
Error::Other(Box::new(error))
|
||||
}
|
||||
|
||||
/// Create a generic error from a string. This is a quick workaround when you are using
|
||||
/// [`Error`] and have a non-Subxt error to return.
|
||||
pub fn other_str(error: impl Into<String>) -> Error {
|
||||
#[derive(thiserror::Error, Debug, Clone)]
|
||||
#[error("{0}")]
|
||||
struct StrError(String);
|
||||
Error::Other(Box::new(StrError(error.into())))
|
||||
}
|
||||
|
||||
/// Checks whether the error was caused by a RPC re-connection.
|
||||
pub fn is_disconnected_will_reconnect(&self) -> bool {
|
||||
matches!(
|
||||
self.backend_error(),
|
||||
Some(BackendError::Rpc(RpcError::ClientError(
|
||||
subxt_rpcs::Error::DisconnectedWillReconnect(_)
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks whether the error was caused by a RPC request being rejected.
|
||||
pub fn is_rpc_limit_reached(&self) -> bool {
|
||||
matches!(
|
||||
self.backend_error(),
|
||||
Some(BackendError::Rpc(RpcError::LimitReached))
|
||||
)
|
||||
}
|
||||
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
Error::BlockError(e) => e.backend_error(),
|
||||
Error::AccountNonceError(e) => e.backend_error(),
|
||||
Error::OnlineClientError(e) => e.backend_error(),
|
||||
Error::RuntimeUpdaterError(e) => e.backend_error(),
|
||||
Error::RuntimeApiError(e) => e.backend_error(),
|
||||
Error::EventsError(e) => e.backend_error(),
|
||||
Error::ExtrinsicError(e) => e.backend_error(),
|
||||
Error::ViewFunctionError(e) => e.backend_error(),
|
||||
Error::TransactionProgressError(e) => e.backend_error(),
|
||||
Error::TransactionEventsError(e) => e.backend_error(),
|
||||
Error::TransactionFinalizedSuccessError(e) => e.backend_error(),
|
||||
Error::StorageError(e) => e.backend_error(),
|
||||
// Any errors that **don't** return a BackendError anywhere will return None:
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum BackendError {
|
||||
#[error("Backend error: RPC error: {0}")]
|
||||
Rpc(#[from] RpcError),
|
||||
#[error("Backend error: Could not find metadata version {0}")]
|
||||
MetadataVersionNotFound(u32),
|
||||
#[error("Backend error: Could not codec::Decode Runtime API response: {0}")]
|
||||
CouldNotScaleDecodeRuntimeResponse(codec::Error),
|
||||
#[error("Backend error: Could not codec::Decode metadata bytes into subxt::Metadata: {0}")]
|
||||
CouldNotDecodeMetadata(codec::Error),
|
||||
// This is for errors in `Backend` implementations which aren't any of the "pre-defined" set above:
|
||||
#[error("Custom backend error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl BackendError {
|
||||
/// Checks whether the error was caused by a RPC re-connection.
|
||||
pub fn is_disconnected_will_reconnect(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Error::Rpc(RpcError::ClientError(
|
||||
BackendError::Rpc(RpcError::ClientError(
|
||||
subxt_rpcs::Error::DisconnectedWillReconnect(_)
|
||||
))
|
||||
)
|
||||
@@ -139,7 +201,13 @@ impl Error {
|
||||
|
||||
/// Checks whether the error was caused by a RPC request being rejected.
|
||||
pub fn is_rpc_limit_reached(&self) -> bool {
|
||||
matches!(self, Error::Rpc(RpcError::LimitReached))
|
||||
matches!(self, BackendError::Rpc(RpcError::LimitReached))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<subxt_rpcs::Error> for BackendError {
|
||||
fn from(value: subxt_rpcs::Error) -> Self {
|
||||
BackendError::Rpc(RpcError::ClientError(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +216,6 @@ impl Error {
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum RpcError {
|
||||
// Dev note: We need the error to be safely sent between threads
|
||||
// for `subscribe_to_block_headers_filling_in_gaps` and friends.
|
||||
/// Error related to the RPC client.
|
||||
#[error("RPC error: {0}")]
|
||||
ClientError(#[from] subxt_rpcs::Error),
|
||||
@@ -157,77 +223,318 @@ pub enum RpcError {
|
||||
/// which is not technically an RPC error but is treated as an error in our own APIs.
|
||||
#[error("RPC error: limit reached")]
|
||||
LimitReached,
|
||||
/// The RPC subscription dropped.
|
||||
/// The RPC subscription was dropped.
|
||||
#[error("RPC error: subscription dropped.")]
|
||||
SubscriptionDropped,
|
||||
}
|
||||
|
||||
/// Block error
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum BlockError {
|
||||
/// An error containing the hash of the block that was not found.
|
||||
#[error("Could not find a block with hash {0} (perhaps it was on a non-finalized fork?)")]
|
||||
NotFound(String),
|
||||
/// Leftover bytes found after decoding the extrinsic.
|
||||
#[error(
|
||||
"After decoding the exntrinsic at index {extrinsic_index}, {num_leftover_bytes} bytes were left, suggesting that decoding may have failed"
|
||||
"Could not find the block body with hash {block_hash} (perhaps it was on a non-finalized fork?)"
|
||||
)]
|
||||
LeftoverBytes {
|
||||
/// Index of the extrinsic that failed to decode.
|
||||
extrinsic_index: usize,
|
||||
/// Number of bytes leftover after decoding the extrinsic.
|
||||
num_leftover_bytes: usize,
|
||||
BlockNotFound { block_hash: Hex },
|
||||
#[error("Could not download the block header with hash {block_hash}: {reason}")]
|
||||
CouldNotGetBlockHeader {
|
||||
block_hash: Hex,
|
||||
reason: BackendError,
|
||||
},
|
||||
/// Decoding error.
|
||||
#[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
|
||||
ExtrinsicDecodeError {
|
||||
/// Index of the extrinsic that failed to decode.
|
||||
extrinsic_index: usize,
|
||||
/// The decode error.
|
||||
error: subxt_core::error::ExtrinsicDecodeError,
|
||||
#[error("Could not download the latest block header: {0}")]
|
||||
CouldNotGetLatestBlock(BackendError),
|
||||
#[error("Could not subscribe to all blocks: {0}")]
|
||||
CouldNotSubscribeToAllBlocks(BackendError),
|
||||
#[error("Could not subscribe to best blocks: {0}")]
|
||||
CouldNotSubscribeToBestBlocks(BackendError),
|
||||
#[error("Could not subscribe to finalized blocks: {0}")]
|
||||
CouldNotSubscribeToFinalizedBlocks(BackendError),
|
||||
#[error("Error getting account nonce at block {block_hash}")]
|
||||
AccountNonceError {
|
||||
block_hash: Hex,
|
||||
account_id: Hex,
|
||||
reason: AccountNonceError,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CoreBlockError> for BlockError {
|
||||
fn from(value: CoreBlockError) -> Self {
|
||||
match value {
|
||||
CoreBlockError::LeftoverBytes {
|
||||
extrinsic_index,
|
||||
num_leftover_bytes,
|
||||
} => BlockError::LeftoverBytes {
|
||||
extrinsic_index,
|
||||
num_leftover_bytes,
|
||||
},
|
||||
CoreBlockError::ExtrinsicDecodeError {
|
||||
extrinsic_index,
|
||||
error,
|
||||
} => BlockError::ExtrinsicDecodeError {
|
||||
extrinsic_index,
|
||||
error,
|
||||
},
|
||||
impl BlockError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
BlockError::CouldNotGetBlockHeader { reason: e, .. }
|
||||
| BlockError::CouldNotGetLatestBlock(e)
|
||||
| BlockError::CouldNotSubscribeToAllBlocks(e)
|
||||
| BlockError::CouldNotSubscribeToBestBlocks(e)
|
||||
| BlockError::CouldNotSubscribeToFinalizedBlocks(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockError {
|
||||
/// Produce an error that a block with the given hash cannot be found.
|
||||
pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError {
|
||||
let hash = format!("0x{}", hex::encode(hash));
|
||||
BlockError::NotFound(hash)
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AccountNonceError {
|
||||
#[error("Could not retrieve account nonce: {0}")]
|
||||
CouldNotRetrieve(#[from] BackendError),
|
||||
#[error("Could not decode account nonce: {0}")]
|
||||
CouldNotDecode(#[from] codec::Error),
|
||||
#[error("Wrong number of account nonce bytes returned: {0} (expected 2, 4 or 8)")]
|
||||
WrongNumberOfBytes(usize),
|
||||
}
|
||||
|
||||
impl AccountNonceError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
AccountNonceError::CouldNotRetrieve(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transaction error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum OnlineClientError {
|
||||
#[error("Cannot construct OnlineClient: {0}")]
|
||||
RpcError(#[from] subxt_rpcs::Error),
|
||||
#[error(
|
||||
"Cannot construct OnlineClient: Cannot fetch latest finalized block to obtain init details from: {0}"
|
||||
)]
|
||||
CannotGetLatestFinalizedBlock(BackendError),
|
||||
#[error("Cannot construct OnlineClient: Cannot fetch genesis hash: {0}")]
|
||||
CannotGetGenesisHash(BackendError),
|
||||
#[error("Cannot construct OnlineClient: Cannot fetch current runtime version: {0}")]
|
||||
CannotGetCurrentRuntimeVersion(BackendError),
|
||||
#[error("Cannot construct OnlineClient: Cannot fetch metadata: {0}")]
|
||||
CannotFetchMetadata(BackendError),
|
||||
}
|
||||
|
||||
impl OnlineClientError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
OnlineClientError::CannotGetLatestFinalizedBlock(e)
|
||||
| OnlineClientError::CannotGetGenesisHash(e)
|
||||
| OnlineClientError::CannotGetCurrentRuntimeVersion(e)
|
||||
| OnlineClientError::CannotFetchMetadata(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum RuntimeUpdaterError {
|
||||
#[error("Error subscribing to runtime updates: The update stream ended unexpectedly")]
|
||||
UnexpectedEndOfUpdateStream,
|
||||
#[error("Error subscribing to runtime updates: The finalized block stream ended unexpectedly")]
|
||||
UnexpectedEndOfBlockStream,
|
||||
#[error("Error subscribing to runtime updates: Can't stream runtime version: {0}")]
|
||||
CannotStreamRuntimeVersion(BackendError),
|
||||
#[error("Error subscribing to runtime updates: Can't get next runtime version in stream: {0}")]
|
||||
CannotGetNextRuntimeVersion(BackendError),
|
||||
#[error("Error subscribing to runtime updates: Cannot stream finalized blocks: {0}")]
|
||||
CannotStreamFinalizedBlocks(BackendError),
|
||||
#[error("Error subscribing to runtime updates: Cannot get next finalized block in stream: {0}")]
|
||||
CannotGetNextFinalizedBlock(BackendError),
|
||||
#[error("Cannot fetch new metadata for runtime update: {0}")]
|
||||
CannotFetchNewMetadata(BackendError),
|
||||
#[error(
|
||||
"Error subscribing to runtime updates: Cannot find the System.LastRuntimeUpgrade storage entry"
|
||||
)]
|
||||
CantFindSystemLastRuntimeUpgrade,
|
||||
#[error("Error subscribing to runtime updates: Cannot fetch last runtime upgrade: {0}")]
|
||||
CantFetchLastRuntimeUpgrade(StorageError),
|
||||
#[error("Error subscribing to runtime updates: Cannot decode last runtime upgrade: {0}")]
|
||||
CannotDecodeLastRuntimeUpgrade(StorageValueError),
|
||||
}
|
||||
|
||||
impl RuntimeUpdaterError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
RuntimeUpdaterError::CannotStreamRuntimeVersion(e)
|
||||
| RuntimeUpdaterError::CannotGetNextRuntimeVersion(e)
|
||||
| RuntimeUpdaterError::CannotStreamFinalizedBlocks(e)
|
||||
| RuntimeUpdaterError::CannotGetNextFinalizedBlock(e)
|
||||
| RuntimeUpdaterError::CannotFetchNewMetadata(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can occur during upgrade.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum RuntimeUpdateeApplyError {
|
||||
#[error("The proposed runtime update is the same as the current version")]
|
||||
SameVersion,
|
||||
}
|
||||
|
||||
/// Error working with Runtime APIs
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum RuntimeApiError {
|
||||
#[error("Cannot access Runtime APIs at latest block: Cannot fetch latest finalized block: {0}")]
|
||||
CannotGetLatestFinalizedBlock(BackendError),
|
||||
#[error("{0}")]
|
||||
OfflineError(#[from] CoreRuntimeApiError),
|
||||
#[error("Cannot call the Runtime API: {0}")]
|
||||
CannotCallApi(BackendError),
|
||||
}
|
||||
|
||||
impl RuntimeApiError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
RuntimeApiError::CannotGetLatestFinalizedBlock(e)
|
||||
| RuntimeApiError::CannotCallApi(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error working with events.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum EventsError {
|
||||
#[error("{0}")]
|
||||
OfflineError(#[from] CoreEventsError),
|
||||
#[error("Cannot access events at latest block: Cannot fetch latest finalized block: {0}")]
|
||||
CannotGetLatestFinalizedBlock(BackendError),
|
||||
#[error("Cannot fetch event bytes: {0}")]
|
||||
CannotFetchEventBytes(BackendError),
|
||||
}
|
||||
|
||||
impl EventsError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
EventsError::CannotGetLatestFinalizedBlock(e)
|
||||
| EventsError::CannotFetchEventBytes(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error working with extrinsics.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ExtrinsicError {
|
||||
#[error("{0}")]
|
||||
OfflineError(#[from] CoreExtrinsicError),
|
||||
#[error("Could not download block body to extract extrinsics from: {0}")]
|
||||
CannotGetBlockBody(BackendError),
|
||||
#[error("Block not found: {0}")]
|
||||
BlockNotFound(Hex),
|
||||
#[error("{0}")]
|
||||
CouldNotDecodeExtrinsics(#[from] ExtrinsicDecodeErrorAt),
|
||||
#[error(
|
||||
"Extrinsic submission error: Cannot get latest finalized block to grab account nonce at: {0}"
|
||||
)]
|
||||
CannotGetLatestFinalizedBlock(BackendError),
|
||||
#[error("Cannot find block header for block {block_hash}")]
|
||||
CannotFindBlockHeader { block_hash: Hex },
|
||||
#[error("Error getting account nonce at block {block_hash}")]
|
||||
AccountNonceError {
|
||||
block_hash: Hex,
|
||||
account_id: Hex,
|
||||
reason: AccountNonceError,
|
||||
},
|
||||
#[error("Cannot submit extrinsic: {0}")]
|
||||
ErrorSubmittingTransaction(BackendError),
|
||||
#[error("A transaction status error was returned while submitting the extrinsic: {0}")]
|
||||
TransactionStatusError(TransactionStatusError),
|
||||
#[error(
|
||||
"The transaction status stream encountered an error while submitting the extrinsic: {0}"
|
||||
)]
|
||||
TransactionStatusStreamError(BackendError),
|
||||
#[error(
|
||||
"The transaction status stream unexpectedly ended, so we don't know the status of the submitted extrinsic"
|
||||
)]
|
||||
UnexpectedEndOfTransactionStatusStream,
|
||||
#[error("Cannot get fee info from Runtime API: {0}")]
|
||||
CannotGetFeeInfo(BackendError),
|
||||
#[error("Cannot get validation info from Runtime API: {0}")]
|
||||
CannotGetValidationInfo(BackendError),
|
||||
#[error("Cannot decode ValidationResult bytes: {0}")]
|
||||
CannotDecodeValidationResult(codec::Error),
|
||||
#[error("ValidationResult bytes could not be decoded")]
|
||||
UnexpectedValidationResultBytes(Vec<u8>),
|
||||
}
|
||||
|
||||
impl ExtrinsicError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
ExtrinsicError::CannotGetBlockBody(e)
|
||||
| ExtrinsicError::CannotGetLatestFinalizedBlock(e)
|
||||
| ExtrinsicError::ErrorSubmittingTransaction(e)
|
||||
| ExtrinsicError::TransactionStatusStreamError(e)
|
||||
| ExtrinsicError::CannotGetFeeInfo(e)
|
||||
| ExtrinsicError::CannotGetValidationInfo(e) => Some(e),
|
||||
ExtrinsicError::AccountNonceError { reason, .. } => reason.backend_error(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error working with View Functions.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ViewFunctionError {
|
||||
#[error("{0}")]
|
||||
OfflineError(#[from] CoreViewFunctionError),
|
||||
#[error(
|
||||
"Cannot access View Functions at latest block: Cannot fetch latest finalized block: {0}"
|
||||
)]
|
||||
CannotGetLatestFinalizedBlock(BackendError),
|
||||
#[error("Cannot call the View Function Runtime API: {0}")]
|
||||
CannotCallApi(BackendError),
|
||||
}
|
||||
|
||||
impl ViewFunctionError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
ViewFunctionError::CannotGetLatestFinalizedBlock(e)
|
||||
| ViewFunctionError::CannotCallApi(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error during the transaction progress.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum TransactionProgressError {
|
||||
#[error("Cannot get the next transaction progress update: {0}")]
|
||||
CannotGetNextProgressUpdate(BackendError),
|
||||
#[error("Error during transaction progress: {0}")]
|
||||
TransactionStatusError(#[from] TransactionStatusError),
|
||||
#[error(
|
||||
"The transaction status stream unexpectedly ended, so we have no further transaction progress updates"
|
||||
)]
|
||||
UnexpectedEndOfTransactionStatusStream,
|
||||
}
|
||||
|
||||
impl TransactionProgressError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
TransactionProgressError::CannotGetNextProgressUpdate(e) => Some(e),
|
||||
TransactionProgressError::TransactionStatusError(_) => None,
|
||||
TransactionProgressError::UnexpectedEndOfTransactionStatusStream => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error emitted as the result of a transaction progress update.
|
||||
#[derive(Clone, Debug, Eq, thiserror::Error, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TransactionError {
|
||||
/// The block hash that the transaction was added to could not be found.
|
||||
/// This is probably because the block was retracted before being finalized.
|
||||
#[error(
|
||||
"The block containing the transaction can no longer be found (perhaps it was on a non-finalized fork?)"
|
||||
)]
|
||||
BlockNotFound,
|
||||
#[allow(missing_docs)]
|
||||
pub enum TransactionStatusError {
|
||||
/// An error happened on the node that the transaction was submitted to.
|
||||
#[error("Error handling transaction: {0}")]
|
||||
Error(String),
|
||||
@@ -238,3 +545,158 @@ pub enum TransactionError {
|
||||
#[error("The transaction was dropped: {0}")]
|
||||
Dropped(String),
|
||||
}
|
||||
|
||||
/// Error fetching events for a just-submitted transaction
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum TransactionEventsError {
|
||||
#[error(
|
||||
"The block containing the submitted transaction ({block_hash}) could not be downloaded: {error}"
|
||||
)]
|
||||
CannotFetchBlockBody {
|
||||
block_hash: Hex,
|
||||
error: BackendError,
|
||||
},
|
||||
#[error(
|
||||
"Cannot find the the submitted transaction (hash: {transaction_hash}) in the block (hash: {block_hash}) it is supposed to be in."
|
||||
)]
|
||||
CannotFindTransactionInBlock {
|
||||
block_hash: Hex,
|
||||
transaction_hash: Hex,
|
||||
},
|
||||
#[error("The block containing the submitted transaction ({block_hash}) could not be found")]
|
||||
BlockNotFound { block_hash: Hex },
|
||||
#[error(
|
||||
"Could not decode event at index {event_index} for the submitted transaction at block {block_hash}: {error}"
|
||||
)]
|
||||
CannotDecodeEventInBlock {
|
||||
event_index: usize,
|
||||
block_hash: Hex,
|
||||
error: EventsError,
|
||||
},
|
||||
#[error("Could not fetch events for the submitted transaction: {error}")]
|
||||
CannotFetchEventsForTransaction {
|
||||
block_hash: Hex,
|
||||
transaction_hash: Hex,
|
||||
error: EventsError,
|
||||
},
|
||||
#[error("The transaction led to a DispatchError, but we failed to decode it: {error}")]
|
||||
CannotDecodeDispatchError {
|
||||
error: DispatchErrorDecodeError,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
#[error("The transaction failed with the following dispatch error: {0}")]
|
||||
ExtrinsicFailed(#[from] DispatchError),
|
||||
}
|
||||
|
||||
impl TransactionEventsError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
TransactionEventsError::CannotFetchBlockBody { error, .. } => Some(error),
|
||||
TransactionEventsError::CannotDecodeEventInBlock { error, .. }
|
||||
| TransactionEventsError::CannotFetchEventsForTransaction { error, .. } => {
|
||||
error.backend_error()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error waiting for the transaction to be finalized and successful.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs, clippy::large_enum_variant)]
|
||||
pub enum TransactionFinalizedSuccessError {
|
||||
#[error("Could not finalize the transaction: {0}")]
|
||||
FinalizationError(#[from] TransactionProgressError),
|
||||
#[error("The transaction did not succeed: {0}")]
|
||||
SuccessError(#[from] TransactionEventsError),
|
||||
}
|
||||
|
||||
impl TransactionFinalizedSuccessError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
TransactionFinalizedSuccessError::FinalizationError(e) => e.backend_error(),
|
||||
TransactionFinalizedSuccessError::SuccessError(e) => e.backend_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error decoding the [`DispatchError`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ModuleErrorDetailsError {
|
||||
#[error(
|
||||
"Could not get details for the DispatchError: could not find pallet index {pallet_index}"
|
||||
)]
|
||||
PalletNotFound { pallet_index: u8 },
|
||||
#[error(
|
||||
"Could not get details for the DispatchError: could not find error index {error_index} in pallet {pallet_name}"
|
||||
)]
|
||||
ErrorVariantNotFound {
|
||||
pallet_name: String,
|
||||
error_index: u8,
|
||||
},
|
||||
}
|
||||
|
||||
/// Error decoding the [`ModuleError`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
#[error("Could not decode the DispatchError::Module payload into the given type: {0}")]
|
||||
pub struct ModuleErrorDecodeError(scale_decode::Error);
|
||||
|
||||
/// Error decoding the [`DispatchError`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum DispatchErrorDecodeError {
|
||||
#[error(
|
||||
"Could not decode the DispatchError: could not find the corresponding type ID in the metadata"
|
||||
)]
|
||||
DispatchErrorTypeIdNotFound,
|
||||
#[error("Could not decode the DispatchError: {0}")]
|
||||
CouldNotDecodeDispatchError(scale_decode::Error),
|
||||
#[error("Could not decode the DispatchError::Module variant")]
|
||||
CouldNotDecodeModuleError {
|
||||
/// The bytes corresponding to the Module variant we were unable to decode:
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Error working with storage.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StorageError {
|
||||
#[error("{0}")]
|
||||
Offline(#[from] CoreStorageError),
|
||||
#[error("Cannot access storage at latest block: Cannot fetch latest finalized block: {0}")]
|
||||
CannotGetLatestFinalizedBlock(BackendError),
|
||||
#[error(
|
||||
"No storage value found at the given address, and no default value to fall back to using."
|
||||
)]
|
||||
NoValueFound,
|
||||
#[error("Cannot fetch the storage value: {0}")]
|
||||
CannotFetchValue(BackendError),
|
||||
#[error("Cannot iterate storage values: {0}")]
|
||||
CannotIterateValues(BackendError),
|
||||
#[error("Encountered an error iterating over storage values: {0}")]
|
||||
StreamFailure(BackendError),
|
||||
#[error("Cannot decode the storage version for a given entry: {0}")]
|
||||
CannotDecodeStorageVersion(codec::Error),
|
||||
}
|
||||
|
||||
impl StorageError {
|
||||
fn backend_error(&self) -> Option<&BackendError> {
|
||||
match self {
|
||||
StorageError::CannotGetLatestFinalizedBlock(e)
|
||||
| StorageError::CannotFetchValue(e)
|
||||
| StorageError::CannotIterateValues(e)
|
||||
| StorageError::StreamFailure(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::backend::{Backend, BackendExt, BlockRef};
|
||||
use crate::{
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::EventsError,
|
||||
events::Events,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
@@ -44,12 +44,14 @@ where
|
||||
pub fn at(
|
||||
&self,
|
||||
block_ref: impl Into<BlockRef<HashFor<T>>>,
|
||||
) -> impl Future<Output = Result<Events<T>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Events<T>, EventsError>> + Send + 'static {
|
||||
self.at_or_latest(Some(block_ref.into()))
|
||||
}
|
||||
|
||||
/// Obtain events for the latest finalized block.
|
||||
pub fn at_latest(&self) -> impl Future<Output = Result<Events<T>, Error>> + Send + 'static {
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Events<T>, EventsError>> + Send + 'static {
|
||||
self.at_or_latest(None)
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ where
|
||||
fn at_or_latest(
|
||||
&self,
|
||||
block_ref: Option<BlockRef<HashFor<T>>>,
|
||||
) -> impl Future<Output = Result<Events<T>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<Events<T>, EventsError>> + Send + 'static {
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
@@ -65,7 +67,11 @@ where
|
||||
// If a block ref isn't provided, we'll get the latest finalized block to use.
|
||||
let block_ref = match block_ref {
|
||||
Some(r) => r,
|
||||
None => client.backend().latest_finalized_block_ref().await?,
|
||||
None => client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(EventsError::CannotGetLatestFinalizedBlock)?,
|
||||
};
|
||||
|
||||
let event_bytes = get_event_bytes(client.backend(), block_ref.hash()).await?;
|
||||
@@ -88,9 +94,11 @@ fn system_events_key() -> [u8; 32] {
|
||||
pub(crate) async fn get_event_bytes<T: Config>(
|
||||
backend: &dyn Backend<T>,
|
||||
block_hash: HashFor<T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
Ok(backend
|
||||
) -> Result<Vec<u8>, EventsError> {
|
||||
let bytes = backend
|
||||
.storage_fetch_value(system_events_key().to_vec(), block_hash)
|
||||
.await?
|
||||
.unwrap_or_default())
|
||||
.await
|
||||
.map_err(EventsError::CannotFetchEventBytes)?
|
||||
.unwrap_or_default();
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
Error, Metadata,
|
||||
Metadata,
|
||||
config::{Config, HashFor},
|
||||
error::EventsError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use scale_decode::DecodeAsType;
|
||||
use scale_decode::{DecodeAsFields, DecodeAsType};
|
||||
use subxt_core::events::{EventDetails as CoreEventDetails, Events as CoreEvents};
|
||||
|
||||
pub use subxt_core::events::{EventMetadataDetails, Phase, StaticEvent};
|
||||
@@ -49,7 +50,7 @@ impl<T: Config> Events<T> {
|
||||
// use of it with our `FilterEvents` stuff.
|
||||
pub fn iter(
|
||||
&self,
|
||||
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
|
||||
) -> impl Iterator<Item = Result<EventDetails<T>, EventsError>> + Send + Sync + 'static {
|
||||
self.inner
|
||||
.iter()
|
||||
.map(|item| item.map(|e| EventDetails { inner: e }).map_err(Into::into))
|
||||
@@ -58,24 +59,24 @@ impl<T: Config> Events<T> {
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return only those which should decode to the provided `Ev` type.
|
||||
/// If an error occurs, all subsequent iterations return `None`.
|
||||
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
|
||||
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
|
||||
self.inner.find::<Ev>().map(|item| item.map_err(Into::into))
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the first event found which decodes to the provided `Ev` type.
|
||||
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.inner.find_first::<Ev>().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Iterate through the events using metadata to dynamically decode and skip
|
||||
/// them, and return the last event found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
|
||||
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
|
||||
self.inner.find_last::<Ev>().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Find an event that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
|
||||
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, EventsError> {
|
||||
self.inner.has::<Ev>().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -138,20 +139,20 @@ impl<T: Config> EventDetails<T> {
|
||||
|
||||
/// Decode and provide the event fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were present in the event.
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
self.inner.field_values().map_err(Into::into)
|
||||
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, EventsError> {
|
||||
self.inner.decode_as_fields().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a type representing the event fields.
|
||||
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
|
||||
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
|
||||
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, EventsError> {
|
||||
self.inner.as_event::<E>().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
|
||||
/// the pallet and event enum variants as well as the event fields). A compatible
|
||||
/// type for this is exposed via static codegen as a root level `Event` type.
|
||||
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, EventsError> {
|
||||
self.inner.as_root_event::<E>().map_err(Into::into)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
mod events_client;
|
||||
mod events_type;
|
||||
|
||||
use crate::Error;
|
||||
use crate::client::OnlineClientT;
|
||||
use crate::error::EventsError;
|
||||
use subxt_core::{
|
||||
Metadata,
|
||||
config::{Config, HashFor},
|
||||
@@ -24,7 +24,7 @@ pub async fn new_events_from_client<T, C>(
|
||||
metadata: Metadata,
|
||||
block_hash: HashFor<T>,
|
||||
client: C,
|
||||
) -> Result<Events<T>, Error>
|
||||
) -> Result<Events<T>, EventsError>
|
||||
where
|
||||
T: Config,
|
||||
C: OnlineClientT<T>,
|
||||
|
||||
+2
-7
@@ -68,17 +68,12 @@ pub mod config {
|
||||
|
||||
/// Types representing the metadata obtained from a node.
|
||||
pub mod metadata {
|
||||
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata};
|
||||
// Expose metadata types under a sub module in case somebody needs to reference them:
|
||||
pub use subxt_metadata as types;
|
||||
pub use subxt_metadata::*;
|
||||
}
|
||||
|
||||
/// Submit dynamic transactions.
|
||||
pub mod dynamic {
|
||||
pub use subxt_core::dynamic::{
|
||||
At, DecodedValue, DecodedValueThunk, Value, constant, runtime_api_call, storage, tx,
|
||||
view_function_call,
|
||||
};
|
||||
pub use subxt_core::dynamic::*;
|
||||
}
|
||||
|
||||
// Expose light client bits
|
||||
|
||||
@@ -9,6 +9,4 @@ mod runtime_types;
|
||||
|
||||
pub use runtime_client::RuntimeApiClient;
|
||||
pub use runtime_types::RuntimeApi;
|
||||
pub use subxt_core::runtime_api::payload::{
|
||||
DefaultPayload, DynamicPayload, Payload, StaticPayload, dynamic,
|
||||
};
|
||||
pub use subxt_core::runtime_api::payload::{DynamicPayload, Payload, StaticPayload, dynamic};
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
backend::BlockRef,
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::RuntimeApiError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
@@ -43,13 +43,17 @@ where
|
||||
/// Obtain a runtime API interface at the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<RuntimeApi<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<RuntimeApi<T, Client>, RuntimeApiError>> + Send + 'static {
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
// get the ref for the latest finalized block and use that.
|
||||
let block_ref = client.backend().latest_finalized_block_ref().await?;
|
||||
let block_ref = client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(RuntimeApiError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
Ok(RuntimeApi::new(client, block_ref))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
backend::BlockRef,
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::RuntimeApiError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
@@ -40,7 +40,7 @@ where
|
||||
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
|
||||
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
|
||||
/// the runtime API in question do not exist at all)
|
||||
pub fn validate<Call: Payload>(&self, payload: &Call) -> Result<(), Error> {
|
||||
pub fn validate<Call: Payload>(&self, payload: Call) -> Result<(), RuntimeApiError> {
|
||||
subxt_core::runtime_api::validate(payload, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ where
|
||||
&self,
|
||||
function: &'a str,
|
||||
call_parameters: Option<&'a [u8]>,
|
||||
) -> impl Future<Output = Result<Vec<u8>, Error>> + use<'a, Client, T> {
|
||||
) -> impl Future<Output = Result<Vec<u8>, RuntimeApiError>> + use<'a, Client, T> {
|
||||
let client = self.client.clone();
|
||||
let block_hash = self.block_ref.hash();
|
||||
// Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(),
|
||||
@@ -59,7 +59,8 @@ where
|
||||
let data = client
|
||||
.backend()
|
||||
.call(function, call_parameters, block_hash)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(RuntimeApiError::CannotCallApi)?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -68,7 +69,8 @@ where
|
||||
pub fn call<Call: Payload>(
|
||||
&self,
|
||||
payload: Call,
|
||||
) -> impl Future<Output = Result<Call::ReturnType, Error>> + use<Call, Client, T> {
|
||||
) -> impl Future<Output = Result<Call::ReturnType, RuntimeApiError>> + use<Call, Client, T>
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let block_hash = self.block_ref.hash();
|
||||
// Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(),
|
||||
@@ -87,7 +89,8 @@ where
|
||||
let bytes = client
|
||||
.backend()
|
||||
.call(&call_name, Some(call_args.as_slice()), block_hash)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(RuntimeApiError::CannotCallApi)?;
|
||||
|
||||
// Decode the response.
|
||||
let value = subxt_core::runtime_api::decode_value(&mut &*bytes, &payload, &metadata)?;
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
//! Types associated with accessing and working with storage items.
|
||||
|
||||
mod storage_client;
|
||||
mod storage_type;
|
||||
mod storage_client_at;
|
||||
|
||||
pub use storage_client::StorageClient;
|
||||
pub use storage_type::{Storage, StorageKeyValuePair};
|
||||
pub use subxt_core::storage::address::{
|
||||
Address, DefaultAddress, DynamicAddress, StaticAddress, StaticStorageKey, StorageKey, dynamic,
|
||||
};
|
||||
pub use storage_client_at::{StorageClientAt, StorageEntryClient, StorageKeyValue, StorageValue};
|
||||
pub use subxt_core::storage::address::{Address, DynamicAddress, StaticAddress, dynamic};
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::storage_type::Storage;
|
||||
use super::storage_client_at::StorageClientAt;
|
||||
use crate::{
|
||||
backend::BlockRef,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::StorageError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
@@ -39,24 +39,9 @@ where
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or storage entry in question do not exist at all).
|
||||
pub fn validate<Addr: Address>(&self, address: &Addr) -> Result<(), Error> {
|
||||
pub fn validate<Addr: Address>(&self, address: &Addr) -> Result<(), StorageError> {
|
||||
subxt_core::storage::validate(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve the entries at the root of the associated address.
|
||||
pub fn address_root_bytes<Addr: Address>(&self, address: &Addr) -> Vec<u8> {
|
||||
subxt_core::storage::get_address_root_bytes(address)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve an entry. This fails if [`Address::append_entry_bytes`] does; in the built-in
|
||||
/// implementation this would be if the pallet and storage entry being asked for is not available on the
|
||||
/// node you're communicating with, or if the metadata is missing some type information (which should not
|
||||
/// happen).
|
||||
pub fn address_bytes<Addr: Address>(&self, address: &Addr) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClient<T, Client>
|
||||
@@ -65,22 +50,27 @@ where
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Obtain storage at some block hash.
|
||||
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> Storage<T, Client> {
|
||||
Storage::new(self.client.clone(), block_ref.into())
|
||||
pub fn at(&self, block_ref: impl Into<BlockRef<HashFor<T>>>) -> StorageClientAt<T, Client> {
|
||||
StorageClientAt::new(self.client.clone(), block_ref.into())
|
||||
}
|
||||
|
||||
/// Obtain storage at the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Storage<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<StorageClientAt<T, Client>, StorageError>> + Send + 'static
|
||||
{
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
// get the ref for the latest finalized block and use that.
|
||||
let block_ref = client.backend().latest_finalized_block_ref().await?;
|
||||
let block_ref = client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(StorageError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
Ok(Storage::new(client, block_ref))
|
||||
Ok(StorageClientAt::new(client, block_ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
// 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.
|
||||
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor},
|
||||
error::StorageError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use futures::StreamExt;
|
||||
use std::marker::PhantomData;
|
||||
use subxt_core::Metadata;
|
||||
use subxt_core::storage::{PrefixOf, address::Address};
|
||||
use subxt_core::utils::{Maybe, Yes};
|
||||
|
||||
pub use subxt_core::storage::{StorageKeyValue, StorageValue};
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct StorageClientAt<T: Config, Client> {
|
||||
client: Client,
|
||||
metadata: Metadata,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClientAt<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientT<T>,
|
||||
{
|
||||
/// Create a new [`StorageClientAt`].
|
||||
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
|
||||
// Retrieve and store metadata here so that we can borrow it in
|
||||
// subsequent structs, and thus also borrow storage info and
|
||||
// things that borrow from metadata.
|
||||
let metadata = client.metadata();
|
||||
|
||||
Self {
|
||||
client,
|
||||
metadata,
|
||||
block_ref,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClientAt<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OfflineClientT<T>,
|
||||
{
|
||||
/// This returns a [`StorageEntryClient`], which allows working with the storage entry at the provided address.
|
||||
pub fn entry<Addr: Address>(
|
||||
&self,
|
||||
address: Addr,
|
||||
) -> Result<StorageEntryClient<'_, T, Client, Addr, Addr::IsPlain>, StorageError> {
|
||||
let inner = subxt_core::storage::entry(address, &self.metadata)?;
|
||||
Ok(StorageEntryClient {
|
||||
inner,
|
||||
client: self.client.clone(),
|
||||
block_ref: self.block_ref.clone(),
|
||||
_marker: core::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> StorageClientAt<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// This is essentially a shorthand for `client.entry(addr)?.fetch(key_parts)`. See [`StorageEntryClient::fetch()`].
|
||||
pub async fn fetch<Addr: Address>(
|
||||
&self,
|
||||
addr: Addr,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<StorageValue<'_, Addr::Value>, StorageError> {
|
||||
let entry = subxt_core::storage::entry(addr, &self.metadata)?;
|
||||
fetch(&entry, &self.client, self.block_ref.hash(), key_parts).await
|
||||
}
|
||||
|
||||
/// This is essentially a shorthand for `client.entry(addr)?.try_fetch(key_parts)`. See [`StorageEntryClient::try_fetch()`].
|
||||
pub async fn try_fetch<Addr: Address>(
|
||||
&self,
|
||||
addr: Addr,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<Option<StorageValue<'_, Addr::Value>>, StorageError> {
|
||||
let entry = subxt_core::storage::entry(addr, &self.metadata)?;
|
||||
try_fetch(&entry, &self.client, self.block_ref.hash(), key_parts).await
|
||||
}
|
||||
|
||||
/// This is essentially a shorthand for `client.entry(addr)?.iter(key_parts)`. See [`StorageEntryClient::iter()`].
|
||||
pub async fn iter<Addr: Address, KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&'_ self,
|
||||
addr: Addr,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageKeyValue<'_, Addr>, StorageError>>
|
||||
+ use<'_, Addr, Client, T, KeyParts>,
|
||||
StorageError,
|
||||
> {
|
||||
let entry = subxt_core::storage::entry(addr, &self.metadata)?;
|
||||
iter(entry, &self.client, self.block_ref.hash(), key_parts).await
|
||||
}
|
||||
|
||||
/// In rare cases, you may wish to fetch a storage value that does not live at a typical address. This method
|
||||
/// is a fallback for those cases, and allows you to provide the raw storage key bytes corresponding to the
|
||||
/// entry you wish to obtain. The response will either be the bytes for the value found at that location, or
|
||||
/// otherwise an error. [`StorageError::NoValueFound`] will be returned in the event that the request was valid
|
||||
/// but no value lives at the given location).
|
||||
pub async fn fetch_raw(&self, key_bytes: Vec<u8>) -> Result<Vec<u8>, StorageError> {
|
||||
let block_hash = self.block_ref.hash();
|
||||
let value = self
|
||||
.client
|
||||
.backend()
|
||||
.storage_fetch_value(key_bytes, block_hash)
|
||||
.await
|
||||
.map_err(StorageError::CannotFetchValue)?
|
||||
.ok_or(StorageError::NoValueFound)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// The storage version of a pallet.
|
||||
/// The storage version refers to the `frame_support::traits::Metadata::StorageVersion` type.
|
||||
pub async fn storage_version(&self, pallet_name: impl AsRef<str>) -> Result<u16, StorageError> {
|
||||
// construct the storage key. This is done similarly in
|
||||
// `frame_support::traits::metadata::StorageVersion::storage_key()`:
|
||||
let mut key_bytes: Vec<u8> = vec![];
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(
|
||||
pallet_name.as_ref().as_bytes(),
|
||||
));
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(b":__STORAGE_VERSION__:"));
|
||||
|
||||
// fetch the raw bytes and decode them into the StorageVersion struct:
|
||||
let storage_version_bytes = self.fetch_raw(key_bytes).await?;
|
||||
|
||||
<u16 as codec::Decode>::decode(&mut &storage_version_bytes[..])
|
||||
.map_err(StorageError::CannotDecodeStorageVersion)
|
||||
}
|
||||
|
||||
/// Fetch the runtime WASM code.
|
||||
pub async fn runtime_wasm_code(&self) -> Result<Vec<u8>, StorageError> {
|
||||
// note: this should match the `CODE` constant in `sp_core::storage::well_known_keys`
|
||||
self.fetch_raw(b":code".to_vec()).await
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents a single storage entry (be it a plain value or map)
|
||||
/// and the operations that can be performed on it.
|
||||
pub struct StorageEntryClient<'atblock, T: Config, Client, Addr, IsPlain> {
|
||||
inner: subxt_core::storage::StorageEntry<'atblock, Addr>,
|
||||
client: Client,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
_marker: PhantomData<(T, IsPlain)>,
|
||||
}
|
||||
|
||||
impl<'atblock, T, Client, Addr, IsPlain> StorageEntryClient<'atblock, T, Client, Addr, IsPlain>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address,
|
||||
{
|
||||
/// Name of the pallet containing this storage entry.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.inner.pallet_name()
|
||||
}
|
||||
|
||||
/// Name of the storage entry.
|
||||
pub fn entry_name(&self) -> &str {
|
||||
self.inner.entry_name()
|
||||
}
|
||||
|
||||
/// Is the storage entry a plain value?
|
||||
pub fn is_plain(&self) -> bool {
|
||||
self.inner.is_plain()
|
||||
}
|
||||
|
||||
/// Is the storage entry a map?
|
||||
pub fn is_map(&self) -> bool {
|
||||
self.inner.is_map()
|
||||
}
|
||||
|
||||
/// Return the default value for this storage entry, if there is one. Returns `None` if there
|
||||
/// is no default value.
|
||||
pub fn default_value(&self) -> Option<StorageValue<'atblock, Addr::Value>> {
|
||||
self.inner.default_value()
|
||||
}
|
||||
}
|
||||
|
||||
// Plain values get a fetch method with no extra arguments.
|
||||
impl<'atblock, T, Client, Addr> StorageEntryClient<'atblock, T, Client, Addr, Yes>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Fetch the storage value at this location. If no value is found, the default value will be returned
|
||||
/// for this entry if one exists. If no value is found and no default value exists, an error will be returned.
|
||||
pub async fn fetch(&self) -> Result<StorageValue<'atblock, Addr::Value>, StorageError> {
|
||||
let value = self.try_fetch().await?.map_or_else(
|
||||
|| self.inner.default_value().ok_or(StorageError::NoValueFound),
|
||||
Ok,
|
||||
)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Fetch the storage value at this location. If no value is found, `None` will be returned.
|
||||
pub async fn try_fetch(
|
||||
&self,
|
||||
) -> Result<Option<StorageValue<'atblock, Addr::Value>>, StorageError> {
|
||||
let value = self
|
||||
.client
|
||||
.backend()
|
||||
.storage_fetch_value(self.key_prefix().to_vec(), self.block_ref.hash())
|
||||
.await
|
||||
.map_err(StorageError::CannotFetchValue)?
|
||||
.map(|bytes| self.inner.value(bytes));
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// This is identical to [`StorageEntryClient::key_prefix()`] and is the full
|
||||
/// key for this storage entry.
|
||||
pub fn key(&self) -> [u8; 32] {
|
||||
self.inner.key_prefix()
|
||||
}
|
||||
|
||||
/// The keys for plain storage values are always 32 byte hashes.
|
||||
pub fn key_prefix(&self) -> [u8; 32] {
|
||||
self.inner.key_prefix()
|
||||
}
|
||||
}
|
||||
|
||||
// When HasDefaultValue = Yes, we expect there to exist a valid default value and will use that
|
||||
// if we fetch an entry and get nothing back.
|
||||
impl<'atblock, T, Client, Addr> StorageEntryClient<'atblock, T, Client, Addr, Maybe>
|
||||
where
|
||||
T: Config,
|
||||
Addr: Address,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Fetch a storage value within this storage entry.
|
||||
///
|
||||
/// This entry may be a map, and so you must provide the relevant values for each part of the storage
|
||||
/// key that is required in order to point to a single value.
|
||||
///
|
||||
/// If no value is found, the default value will be returned for this entry if one exists. If no value is
|
||||
/// found and no default value exists, an error will be returned.
|
||||
pub async fn fetch(
|
||||
&self,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<StorageValue<'atblock, Addr::Value>, StorageError> {
|
||||
fetch(&self.inner, &self.client, self.block_ref.hash(), key_parts).await
|
||||
}
|
||||
|
||||
/// Fetch a storage value within this storage entry.
|
||||
///
|
||||
/// This entry may be a map, and so you must provide the relevant values for each part of the storage
|
||||
/// key that is required in order to point to a single value.
|
||||
///
|
||||
/// If no value is found, `None` will be returned.
|
||||
pub async fn try_fetch(
|
||||
&self,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<Option<StorageValue<'atblock, Addr::Value>>, StorageError> {
|
||||
try_fetch(&self.inner, &self.client, self.block_ref.hash(), key_parts).await
|
||||
}
|
||||
|
||||
/// Iterate over storage values within this storage entry.
|
||||
///
|
||||
/// You may provide any prefix of the values needed to point to a single value. Normally you will
|
||||
/// provide `()` to iterate over _everything_, or `(first_key,)` to iterate over everything underneath
|
||||
/// `first_key` in the map, or `(first_key, second_key)` to iterate over everything underneath `first_key`
|
||||
/// and `second_key` in the map, and so on, up to the actual depth of the map - 1.
|
||||
pub async fn iter<KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageKeyValue<'atblock, Addr>, StorageError>>
|
||||
+ use<'atblock, Addr, Client, T, KeyParts>,
|
||||
StorageError,
|
||||
> {
|
||||
iter(
|
||||
self.inner.clone(),
|
||||
&self.client,
|
||||
self.block_ref.hash(),
|
||||
key_parts,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// This returns a full key to a single value in this storage entry.
|
||||
pub fn key(&self, key_parts: Addr::KeyParts) -> Result<Vec<u8>, StorageError> {
|
||||
let key = self.inner.fetch_key(key_parts)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// This returns valid keys to iterate over the storage entry at the available levels.
|
||||
pub fn iter_key<KeyParts: PrefixOf<Addr::KeyParts>>(
|
||||
&self,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<Vec<u8>, StorageError> {
|
||||
let key = self.inner.iter_key(key_parts)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// The first 32 bytes of the storage entry key, which points to the entry but not necessarily
|
||||
/// a single storage value (unless the entry is a plain value).
|
||||
pub fn key_prefix(&self) -> [u8; 32] {
|
||||
self.inner.key_prefix()
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch<'atblock, T: Config, Client: OnlineClientT<T>, Addr: Address>(
|
||||
entry: &subxt_core::storage::StorageEntry<'atblock, Addr>,
|
||||
client: &Client,
|
||||
block_hash: HashFor<T>,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<StorageValue<'atblock, Addr::Value>, StorageError> {
|
||||
let value = try_fetch(entry, client, block_hash, key_parts)
|
||||
.await?
|
||||
.or_else(|| entry.default_value())
|
||||
.unwrap();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
async fn try_fetch<'atblock, T: Config, Client: OnlineClientT<T>, Addr: Address>(
|
||||
entry: &subxt_core::storage::StorageEntry<'atblock, Addr>,
|
||||
client: &Client,
|
||||
block_hash: HashFor<T>,
|
||||
key_parts: Addr::KeyParts,
|
||||
) -> Result<Option<StorageValue<'atblock, Addr::Value>>, StorageError> {
|
||||
let key = entry.fetch_key(key_parts)?;
|
||||
|
||||
let value = client
|
||||
.backend()
|
||||
.storage_fetch_value(key, block_hash)
|
||||
.await
|
||||
.map_err(StorageError::CannotFetchValue)?
|
||||
.map(|bytes| entry.value(bytes))
|
||||
.or_else(|| entry.default_value());
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
async fn iter<
|
||||
'atblock,
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
Addr: Address,
|
||||
KeyParts: PrefixOf<Addr::KeyParts>,
|
||||
>(
|
||||
entry: subxt_core::storage::StorageEntry<'atblock, Addr>,
|
||||
client: &Client,
|
||||
block_hash: HashFor<T>,
|
||||
key_parts: KeyParts,
|
||||
) -> Result<
|
||||
impl futures::Stream<Item = Result<StorageKeyValue<'atblock, Addr>, StorageError>>
|
||||
+ use<'atblock, Addr, Client, T, KeyParts>,
|
||||
StorageError,
|
||||
> {
|
||||
let key_bytes = entry.iter_key(key_parts)?;
|
||||
|
||||
let stream = client
|
||||
.backend()
|
||||
.storage_fetch_descendant_values(key_bytes, block_hash)
|
||||
.await
|
||||
.map_err(StorageError::CannotIterateValues)?
|
||||
.map(move |kv| {
|
||||
let kv = match kv {
|
||||
Ok(kv) => kv,
|
||||
Err(e) => return Err(StorageError::StreamFailure(e)),
|
||||
};
|
||||
Ok(entry.key_value(kv.key, kv.value))
|
||||
});
|
||||
|
||||
Ok(Box::pin(stream))
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef},
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::{Error, MetadataError, StorageAddressError},
|
||||
metadata::DecodeWithMetadata,
|
||||
};
|
||||
use codec::Decode;
|
||||
use derive_where::derive_where;
|
||||
use futures::StreamExt;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use subxt_core::storage::address::{Address, StorageHashers, StorageKey};
|
||||
use subxt_core::utils::Yes;
|
||||
|
||||
/// This is returned from a couple of storage functions.
|
||||
pub use crate::backend::StreamOfResults;
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct Storage<T: Config, Client> {
|
||||
client: Client,
|
||||
block_ref: BlockRef<HashFor<T>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Config, Client> Storage<T, Client> {
|
||||
/// Create a new [`Storage`]
|
||||
pub(crate) fn new(client: Client, block_ref: BlockRef<HashFor<T>>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
block_ref,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Client> Storage<T, Client>
|
||||
where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// Fetch the raw encoded value at the key given.
|
||||
pub fn fetch_raw(
|
||||
&self,
|
||||
key: impl Into<Vec<u8>>,
|
||||
) -> impl Future<Output = Result<Option<Vec<u8>>, Error>> + 'static {
|
||||
let client = self.client.clone();
|
||||
let key = key.into();
|
||||
// Keep this alive until the call is complete:
|
||||
let block_ref = self.block_ref.clone();
|
||||
// Manual future so lifetime not tied to api.storage().
|
||||
async move {
|
||||
let data = client
|
||||
.backend()
|
||||
.storage_fetch_value(key, block_ref.hash())
|
||||
.await?;
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream all of the raw keys underneath the key given
|
||||
pub fn fetch_raw_keys(
|
||||
&self,
|
||||
key: impl Into<Vec<u8>>,
|
||||
) -> impl Future<Output = Result<StreamOfResults<Vec<u8>>, Error>> + 'static {
|
||||
let client = self.client.clone();
|
||||
let block_hash = self.block_ref.hash();
|
||||
let key = key.into();
|
||||
// Manual future so lifetime not tied to api.storage().
|
||||
async move {
|
||||
let keys = client
|
||||
.backend()
|
||||
.storage_fetch_descendant_keys(key, block_hash)
|
||||
.await?;
|
||||
Ok(keys)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a decoded value from storage at a given address.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// use subxt::{ PolkadotConfig, OnlineClient };
|
||||
///
|
||||
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
|
||||
/// pub mod polkadot {}
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
|
||||
///
|
||||
/// // Address to a storage entry we'd like to access.
|
||||
/// let address = polkadot::storage().xcm_pallet().queries(12345);
|
||||
///
|
||||
/// // Fetch just the keys, returning up to 10 keys.
|
||||
/// let value = api
|
||||
/// .storage()
|
||||
/// .at_latest()
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .fetch(&address)
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
///
|
||||
/// println!("Value: {:?}", value);
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn fetch<'address, Addr>(
|
||||
&self,
|
||||
address: &'address Addr,
|
||||
) -> impl Future<Output = Result<Option<Addr::Target>, Error>> + use<'address, Addr, Client, T>
|
||||
where
|
||||
Addr: Address<IsFetchable = Yes> + 'address,
|
||||
{
|
||||
let client = self.clone();
|
||||
async move {
|
||||
let metadata = client.client.metadata();
|
||||
|
||||
// Metadata validation checks whether the static address given
|
||||
// is likely to actually correspond to a real storage entry or not.
|
||||
// if not, it means static codegen doesn't line up with runtime
|
||||
// metadata.
|
||||
subxt_core::storage::validate(address, &metadata)?;
|
||||
|
||||
// Look up the return type ID to enable DecodeWithMetadata:
|
||||
let lookup_bytes = subxt_core::storage::get_address_bytes(address, &metadata)?;
|
||||
if let Some(data) = client.fetch_raw(lookup_bytes).await? {
|
||||
let val = subxt_core::storage::decode_value(&mut &*data, address, &metadata)?;
|
||||
Ok(Some(val))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a StorageKey that has a default value with an optional block hash.
|
||||
pub fn fetch_or_default<'address, Addr>(
|
||||
&self,
|
||||
address: &'address Addr,
|
||||
) -> impl Future<Output = Result<Addr::Target, Error>> + use<'address, Addr, Client, T>
|
||||
where
|
||||
Addr: Address<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
|
||||
{
|
||||
let client = self.clone();
|
||||
async move {
|
||||
// Metadata validation happens via .fetch():
|
||||
if let Some(data) = client.fetch(address).await? {
|
||||
Ok(data)
|
||||
} else {
|
||||
let metadata = client.client.metadata();
|
||||
let val = subxt_core::storage::default_value(address, &metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of key value pairs.
|
||||
///
|
||||
/// ```rust,no_run,standalone_crate
|
||||
/// use subxt::{ PolkadotConfig, OnlineClient };
|
||||
///
|
||||
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
|
||||
/// pub mod polkadot {}
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
|
||||
///
|
||||
/// // Address to the root of a storage entry that we'd like to iterate over.
|
||||
/// let address = polkadot::storage().xcm_pallet().version_notifiers_iter();
|
||||
///
|
||||
/// // Iterate over keys and values at that address.
|
||||
/// let mut iter = api
|
||||
/// .storage()
|
||||
/// .at_latest()
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .iter(address)
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
///
|
||||
/// while let Some(Ok(kv)) = iter.next().await {
|
||||
/// println!("Key bytes: 0x{}", hex::encode(&kv.key_bytes));
|
||||
/// println!("Value: {}", kv.value);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn iter<Addr>(
|
||||
&self,
|
||||
address: Addr,
|
||||
) -> impl Future<Output = Result<StreamOfResults<StorageKeyValuePair<Addr>>, Error>> + 'static
|
||||
where
|
||||
Addr: Address<IsIterable = Yes> + 'static,
|
||||
Addr::Keys: 'static + Sized,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let block_ref = self.block_ref.clone();
|
||||
async move {
|
||||
let metadata = client.metadata();
|
||||
let (_pallet, entry) = subxt_core::storage::lookup_storage_entry_details(
|
||||
address.pallet_name(),
|
||||
address.entry_name(),
|
||||
&metadata,
|
||||
)?;
|
||||
|
||||
// Metadata validation checks whether the static address given
|
||||
// is likely to actually correspond to a real storage entry or not.
|
||||
// if not, it means static codegen doesn't line up with runtime
|
||||
// metadata.
|
||||
subxt_core::storage::validate(&address, &metadata)?;
|
||||
|
||||
// Look up the return type for flexible decoding. Do this once here to avoid
|
||||
// potentially doing it every iteration if we used `decode_storage_with_metadata`
|
||||
// in the iterator.
|
||||
let entry = entry.entry_type();
|
||||
|
||||
let return_type_id = entry.value_ty();
|
||||
let hashers = StorageHashers::new(entry, metadata.types())?;
|
||||
|
||||
// The address bytes of this entry:
|
||||
let address_bytes = subxt_core::storage::get_address_bytes(&address, &metadata)?;
|
||||
let s = client
|
||||
.backend()
|
||||
.storage_fetch_descendant_values(address_bytes, block_ref.hash())
|
||||
.await?
|
||||
.map(move |kv| {
|
||||
let kv = match kv {
|
||||
Ok(kv) => kv,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
let value = Addr::Target::decode_with_metadata(
|
||||
&mut &*kv.value,
|
||||
return_type_id,
|
||||
&metadata,
|
||||
)?;
|
||||
|
||||
let key_bytes = kv.key;
|
||||
let cursor = &mut &key_bytes[..];
|
||||
strip_storage_address_root_bytes(cursor)?;
|
||||
|
||||
let keys = <Addr::Keys as StorageKey>::decode_storage_key(
|
||||
cursor,
|
||||
&mut hashers.iter(),
|
||||
metadata.types(),
|
||||
)?;
|
||||
|
||||
Ok(StorageKeyValuePair::<Addr> {
|
||||
keys,
|
||||
key_bytes,
|
||||
value,
|
||||
})
|
||||
});
|
||||
|
||||
let s = StreamOfResults::new(Box::pin(s));
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// The storage version of a pallet.
|
||||
/// The storage version refers to the `frame_support::traits::Metadata::StorageVersion` type.
|
||||
pub async fn storage_version(&self, pallet_name: impl AsRef<str>) -> Result<u16, Error> {
|
||||
// check that the pallet exists in the metadata:
|
||||
self.client
|
||||
.metadata()
|
||||
.pallet_by_name(pallet_name.as_ref())
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(pallet_name.as_ref().into()))?;
|
||||
|
||||
// construct the storage key. This is done similarly in `frame_support::traits::metadata::StorageVersion::storage_key()`.
|
||||
pub const STORAGE_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__STORAGE_VERSION__:";
|
||||
let mut key_bytes: Vec<u8> = vec![];
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(
|
||||
pallet_name.as_ref().as_bytes(),
|
||||
));
|
||||
key_bytes.extend(&sp_crypto_hashing::twox_128(
|
||||
STORAGE_VERSION_STORAGE_KEY_POSTFIX,
|
||||
));
|
||||
|
||||
// fetch the raw bytes and decode them into the StorageVersion struct:
|
||||
let storage_version_bytes = self.fetch_raw(key_bytes).await?.ok_or_else(|| {
|
||||
format!(
|
||||
"Unexpected: entry for storage version in pallet \"{}\" not found",
|
||||
pallet_name.as_ref()
|
||||
)
|
||||
})?;
|
||||
u16::decode(&mut &storage_version_bytes[..]).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Fetch the runtime WASM code.
|
||||
pub async fn runtime_wasm_code(&self) -> Result<Vec<u8>, Error> {
|
||||
// note: this should match the `CODE` constant in `sp_core::storage::well_known_keys`
|
||||
const CODE: &str = ":code";
|
||||
self.fetch_raw(CODE.as_bytes()).await?.ok_or_else(|| {
|
||||
format!("Unexpected: entry for well known key \"{CODE}\" not found").into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Strips the first 32 bytes (16 for the pallet hash, 16 for the entry hash) off some storage address bytes.
|
||||
fn strip_storage_address_root_bytes(address_bytes: &mut &[u8]) -> Result<(), StorageAddressError> {
|
||||
if address_bytes.len() >= 32 {
|
||||
*address_bytes = &address_bytes[32..];
|
||||
Ok(())
|
||||
} else {
|
||||
Err(StorageAddressError::UnexpectedAddressBytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of keys and values together with all the bytes that make up the storage address.
|
||||
/// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct StorageKeyValuePair<T: Address> {
|
||||
/// The bytes that make up the address of the storage entry.
|
||||
pub key_bytes: Vec<u8>,
|
||||
/// The keys that can be used to construct the address of this storage entry.
|
||||
pub keys: T::Keys,
|
||||
/// The value of the storage entry.
|
||||
pub value: T::Target,
|
||||
}
|
||||
+104
-52
@@ -6,13 +6,13 @@ use crate::{
|
||||
backend::{BackendExt, BlockRef, TransactionStatus},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, ExtrinsicParams, HashFor, Header},
|
||||
error::{BlockError, Error},
|
||||
error::{ExtrinsicError, TransactionStatusError},
|
||||
tx::{Payload, Signer as SignerT, TxProgress},
|
||||
utils::PhantomDataSendSync,
|
||||
};
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use derive_where::derive_where;
|
||||
use futures::future::try_join;
|
||||
use futures::future::{TryFutureExt, try_join};
|
||||
use subxt_core::tx::TransactionVersion;
|
||||
|
||||
/// A client for working with transactions.
|
||||
@@ -37,7 +37,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
|
||||
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or call in question do not exist at all).
|
||||
pub fn validate<Call>(&self, call: &Call) -> Result<(), Error>
|
||||
pub fn validate<Call>(&self, call: &Call) -> Result<(), ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -45,7 +45,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
}
|
||||
|
||||
/// Return the SCALE encoded bytes representing the call data of the transaction.
|
||||
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
|
||||
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -55,7 +55,10 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
/// Creates an unsigned transaction without submitting it. Depending on the metadata, we might end
|
||||
/// up constructing either a v4 or v5 transaction. See [`Self::create_v4_unsigned`] or
|
||||
/// [`Self::create_v5_bare`] if you'd like to explicitly create an unsigned transaction of a certain version.
|
||||
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableTransaction<T, C>, Error>
|
||||
pub fn create_unsigned<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -78,7 +81,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
pub fn create_v4_unsigned<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
) -> Result<SubmittableTransaction<T, C>, Error>
|
||||
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -95,7 +98,10 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
///
|
||||
/// Prefer [`Self::create_unsigned()`] if you don't know which version to create; this will pick the
|
||||
/// most suitable one for the given chain.
|
||||
pub fn create_v5_bare<Call>(&self, call: &Call) -> Result<SubmittableTransaction<T, C>, Error>
|
||||
pub fn create_v5_bare<Call>(
|
||||
&self,
|
||||
call: &Call,
|
||||
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -117,7 +123,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, Error>
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -148,7 +154,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, Error>
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -175,7 +181,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
&self,
|
||||
call: &Call,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, Error>
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -198,9 +204,21 @@ where
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
/// Get the account nonce for a given account ID.
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
|
||||
let block_ref = self.client.backend().latest_finalized_block_ref().await?;
|
||||
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash()).await
|
||||
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, ExtrinsicError> {
|
||||
let block_ref = self
|
||||
.client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
crate::blocks::get_account_nonce(&self.client, account_id, block_ref.hash())
|
||||
.await
|
||||
.map_err(|e| ExtrinsicError::AccountNonceError {
|
||||
block_hash: block_ref.hash().into(),
|
||||
account_id: account_id.encode().into(),
|
||||
reason: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a partial transaction, without submitting it. This can then be signed and submitted.
|
||||
@@ -209,7 +227,7 @@ where
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, Error>
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -226,7 +244,7 @@ where
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, Error>
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -243,7 +261,7 @@ where
|
||||
call: &Call,
|
||||
account_id: &T::AccountId,
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T, C>, Error>
|
||||
) -> Result<PartialTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
{
|
||||
@@ -257,7 +275,7 @@ where
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SubmittableTransaction<T, C>, Error>
|
||||
) -> Result<SubmittableTransaction<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
Signer: SignerT<T>,
|
||||
@@ -278,7 +296,7 @@ where
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
) -> Result<TxProgress<T, C>, Error>
|
||||
) -> Result<TxProgress<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
Signer: SignerT<T>,
|
||||
@@ -297,7 +315,7 @@ where
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<TxProgress<T, C>, Error>
|
||||
) -> Result<TxProgress<T, C>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
Signer: SignerT<T>,
|
||||
@@ -322,7 +340,7 @@ where
|
||||
&mut self,
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
) -> Result<HashFor<T>, Error>
|
||||
) -> Result<HashFor<T>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
Signer: SignerT<T>,
|
||||
@@ -345,7 +363,7 @@ where
|
||||
call: &Call,
|
||||
signer: &Signer,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<HashFor<T>, Error>
|
||||
) -> Result<HashFor<T>, ExtrinsicError>
|
||||
where
|
||||
Call: Payload,
|
||||
Signer: SignerT<T>,
|
||||
@@ -485,7 +503,7 @@ where
|
||||
///
|
||||
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
|
||||
/// and obtain details about it, once it has made it into a block.
|
||||
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, Error> {
|
||||
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, ExtrinsicError> {
|
||||
// Get a hash of the transaction (we'll need this later).
|
||||
let ext_hash = self.hash();
|
||||
|
||||
@@ -494,7 +512,8 @@ where
|
||||
.client
|
||||
.backend()
|
||||
.submit_transaction(self.encoded())
|
||||
.await?;
|
||||
.await
|
||||
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
|
||||
|
||||
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
|
||||
}
|
||||
@@ -504,13 +523,14 @@ where
|
||||
/// It's usually better to call `submit_and_watch` to get an idea of the progress of the
|
||||
/// submission and whether it's eventually successful or not. This call does not guarantee
|
||||
/// success, and is just sending the transaction to the chain.
|
||||
pub async fn submit(&self) -> Result<HashFor<T>, Error> {
|
||||
pub async fn submit(&self) -> Result<HashFor<T>, ExtrinsicError> {
|
||||
let ext_hash = self.hash();
|
||||
let mut sub = self
|
||||
.client
|
||||
.backend()
|
||||
.submit_transaction(self.encoded())
|
||||
.await?;
|
||||
.await
|
||||
.map_err(ExtrinsicError::ErrorSubmittingTransaction)?;
|
||||
|
||||
// If we get a bad status or error back straight away then error, else return the hash.
|
||||
match sub.next().await {
|
||||
@@ -520,20 +540,22 @@ where
|
||||
| TransactionStatus::InBestBlock { .. }
|
||||
| TransactionStatus::NoLongerInBestBlock
|
||||
| TransactionStatus::InFinalizedBlock { .. } => Ok(ext_hash),
|
||||
TransactionStatus::Error { message } => {
|
||||
Err(Error::Other(format!("Transaction error: {message}")))
|
||||
}
|
||||
TransactionStatus::Error { message } => Err(
|
||||
ExtrinsicError::TransactionStatusError(TransactionStatusError::Error(message)),
|
||||
),
|
||||
TransactionStatus::Invalid { message } => {
|
||||
Err(Error::Other(format!("Transaction invalid: {message}")))
|
||||
Err(ExtrinsicError::TransactionStatusError(
|
||||
TransactionStatusError::Invalid(message),
|
||||
))
|
||||
}
|
||||
TransactionStatus::Dropped { message } => {
|
||||
Err(Error::Other(format!("Transaction dropped: {message}")))
|
||||
Err(ExtrinsicError::TransactionStatusError(
|
||||
TransactionStatusError::Dropped(message),
|
||||
))
|
||||
}
|
||||
},
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Err(Error::Other(
|
||||
"Transaction broadcast was unsuccessful; stream terminated early".into(),
|
||||
)),
|
||||
Some(Err(e)) => Err(ExtrinsicError::TransactionStatusStreamError(e)),
|
||||
None => Err(ExtrinsicError::UnexpectedEndOfTransactionStatusStream),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,8 +563,13 @@ where
|
||||
/// valid can be added to a block, but may still end up in an error state.
|
||||
///
|
||||
/// Returns `Ok` with a [`ValidationResult`], which is the result of attempting to dry run the transaction.
|
||||
pub async fn validate(&self) -> Result<ValidationResult, Error> {
|
||||
let latest_block_ref = self.client.backend().latest_finalized_block_ref().await?;
|
||||
pub async fn validate(&self) -> Result<ValidationResult, ExtrinsicError> {
|
||||
let latest_block_ref = self
|
||||
.client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
|
||||
self.validate_at(latest_block_ref).await
|
||||
}
|
||||
|
||||
@@ -553,7 +580,7 @@ where
|
||||
pub async fn validate_at(
|
||||
&self,
|
||||
at: impl Into<BlockRef<HashFor<T>>>,
|
||||
) -> Result<ValidationResult, Error> {
|
||||
) -> Result<ValidationResult, ExtrinsicError> {
|
||||
let block_hash = at.into().hash();
|
||||
|
||||
// Approach taken from https://github.com/paritytech/json-rpc-interface-spec/issues/55.
|
||||
@@ -570,17 +597,23 @@ where
|
||||
Some(¶ms),
|
||||
block_hash,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetValidationInfo)?;
|
||||
|
||||
ValidationResult::try_from_bytes(res)
|
||||
}
|
||||
|
||||
/// This returns an estimate for what the transaction is expected to cost to execute, less any tips.
|
||||
/// The actual amount paid can vary from block to block based on node traffic and other factors.
|
||||
pub async fn partial_fee_estimate(&self) -> Result<u128, Error> {
|
||||
pub async fn partial_fee_estimate(&self) -> Result<u128, ExtrinsicError> {
|
||||
let mut params = self.encoded().to_vec();
|
||||
(self.encoded().len() as u32).encode_to(&mut params);
|
||||
let latest_block_ref = self.client.backend().latest_finalized_block_ref().await?;
|
||||
let latest_block_ref = self
|
||||
.client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
// destructuring RuntimeDispatchInfo, see type information <https://paritytech.github.io/substrate/master/pallet_transaction_payment_rpc_runtime_api/struct.RuntimeDispatchInfo.html>
|
||||
// data layout: {weight_ref_time: Compact<u64>, weight_proof_size: Compact<u64>, class: u8, partial_fee: u128}
|
||||
@@ -592,7 +625,9 @@ where
|
||||
Some(¶ms),
|
||||
latest_block_ref.hash(),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetFeeInfo)?;
|
||||
|
||||
Ok(partial_fee)
|
||||
}
|
||||
}
|
||||
@@ -602,19 +637,33 @@ async fn inject_account_nonce_and_block<T: Config, Client: OnlineClientT<T>>(
|
||||
client: &Client,
|
||||
account_id: &T::AccountId,
|
||||
params: &mut <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), ExtrinsicError> {
|
||||
use subxt_core::config::transaction_extensions::Params;
|
||||
|
||||
let block_ref = client.backend().latest_finalized_block_ref().await?;
|
||||
let block_ref = client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
let (block_header, account_nonce) = try_join(
|
||||
client.backend().block_header(block_ref.hash()),
|
||||
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()),
|
||||
client
|
||||
.backend()
|
||||
.block_header(block_ref.hash())
|
||||
.map_err(ExtrinsicError::CannotGetLatestFinalizedBlock),
|
||||
crate::blocks::get_account_nonce(client, account_id, block_ref.hash()).map_err(|e| {
|
||||
ExtrinsicError::AccountNonceError {
|
||||
block_hash: block_ref.hash().into(),
|
||||
account_id: account_id.encode().into(),
|
||||
reason: e,
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let block_header =
|
||||
block_header.ok_or_else(|| Error::Block(BlockError::not_found(block_ref.hash())))?;
|
||||
let block_header = block_header.ok_or_else(|| ExtrinsicError::CannotFindBlockHeader {
|
||||
block_hash: block_ref.hash().into(),
|
||||
})?;
|
||||
|
||||
params.inject_account_nonce(account_nonce);
|
||||
params.inject_block(block_header.number().into(), block_ref.hash());
|
||||
@@ -624,26 +673,29 @@ async fn inject_account_nonce_and_block<T: Config, Client: OnlineClientT<T>>(
|
||||
|
||||
impl ValidationResult {
|
||||
#[allow(clippy::get_first)]
|
||||
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, crate::Error> {
|
||||
fn try_from_bytes(bytes: Vec<u8>) -> Result<ValidationResult, ExtrinsicError> {
|
||||
// TaggedTransactionQueue_validate_transaction returns this:
|
||||
// https://github.com/paritytech/substrate/blob/0cdf7029017b70b7c83c21a4dc0aa1020e7914f6/primitives/runtime/src/transaction_validity.rs#L210
|
||||
// We copy some of the inner types and put the three states (valid, invalid, unknown) into one enum,
|
||||
// because from our perspective, the call was successful regardless.
|
||||
if bytes.get(0) == Some(&0) {
|
||||
// ok: valid. Decode but, for now we discard most of the information
|
||||
let res = TransactionValid::decode(&mut &bytes[1..])?;
|
||||
let res = TransactionValid::decode(&mut &bytes[1..])
|
||||
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
|
||||
Ok(ValidationResult::Valid(res))
|
||||
} else if bytes.get(0) == Some(&1) && bytes.get(1) == Some(&0) {
|
||||
// error: invalid
|
||||
let res = TransactionInvalid::decode(&mut &bytes[2..])?;
|
||||
let res = TransactionInvalid::decode(&mut &bytes[2..])
|
||||
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
|
||||
Ok(ValidationResult::Invalid(res))
|
||||
} else if bytes.get(0) == Some(&1) && bytes.get(1) == Some(&1) {
|
||||
// error: unknown
|
||||
let res = TransactionUnknown::decode(&mut &bytes[2..])?;
|
||||
let res = TransactionUnknown::decode(&mut &bytes[2..])
|
||||
.map_err(ExtrinsicError::CannotDecodeValidationResult)?;
|
||||
Ok(ValidationResult::Unknown(res))
|
||||
} else {
|
||||
// unable to decode the bytes; they aren't what we expect.
|
||||
Err(crate::Error::Unknown(bytes))
|
||||
Err(ExtrinsicError::UnexpectedValidationResultBytes(bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+89
-49
@@ -10,7 +10,10 @@ use crate::{
|
||||
backend::{BlockRef, StreamOfResults, TransactionStatus as BackendTxStatus},
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::{DispatchError, Error, RpcError, TransactionError},
|
||||
error::{
|
||||
DispatchError, TransactionEventsError, TransactionFinalizedSuccessError,
|
||||
TransactionProgressError, TransactionStatusError,
|
||||
},
|
||||
events::EventsClient,
|
||||
utils::strip_compact_prefix,
|
||||
};
|
||||
@@ -67,7 +70,7 @@ where
|
||||
/// Return the next transaction status when it's emitted. This just delegates to the
|
||||
/// [`futures::Stream`] implementation for [`TxProgress`], but allows you to
|
||||
/// avoid importing that trait if you don't otherwise need it.
|
||||
pub async fn next(&mut self) -> Option<Result<TxStatus<T, C>, Error>> {
|
||||
pub async fn next(&mut self) -> Option<Result<TxStatus<T, C>, TransactionProgressError>> {
|
||||
StreamExt::next(self).await
|
||||
}
|
||||
|
||||
@@ -81,24 +84,26 @@ where
|
||||
/// probability that the transaction will not make it into a block but there is no guarantee
|
||||
/// that this is true. In those cases the stream is closed however, so you currently have no way to find
|
||||
/// out if they finally made it into a block or not.
|
||||
pub async fn wait_for_finalized(mut self) -> Result<TxInBlock<T, C>, Error> {
|
||||
pub async fn wait_for_finalized(mut self) -> Result<TxInBlock<T, C>, TransactionProgressError> {
|
||||
while let Some(status) = self.next().await {
|
||||
match status? {
|
||||
// Finalized! Return.
|
||||
TxStatus::InFinalizedBlock(s) => return Ok(s),
|
||||
// Error scenarios; return the error.
|
||||
TxStatus::Error { message } => return Err(TransactionError::Error(message).into()),
|
||||
TxStatus::Error { message } => {
|
||||
return Err(TransactionStatusError::Error(message).into());
|
||||
}
|
||||
TxStatus::Invalid { message } => {
|
||||
return Err(TransactionError::Invalid(message).into());
|
||||
return Err(TransactionStatusError::Invalid(message).into());
|
||||
}
|
||||
TxStatus::Dropped { message } => {
|
||||
return Err(TransactionError::Dropped(message).into());
|
||||
return Err(TransactionStatusError::Dropped(message).into());
|
||||
}
|
||||
// Ignore and wait for next status event:
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
Err(RpcError::SubscriptionDropped.into())
|
||||
Err(TransactionProgressError::UnexpectedEndOfTransactionStatusStream)
|
||||
}
|
||||
|
||||
/// Wait for the transaction to be finalized, and for the transaction events to indicate
|
||||
@@ -114,14 +119,14 @@ where
|
||||
/// out if they finally made it into a block or not.
|
||||
pub async fn wait_for_finalized_success(
|
||||
self,
|
||||
) -> Result<crate::blocks::ExtrinsicEvents<T>, Error> {
|
||||
) -> Result<crate::blocks::ExtrinsicEvents<T>, TransactionFinalizedSuccessError> {
|
||||
let evs = self.wait_for_finalized().await?.wait_for_success().await?;
|
||||
Ok(evs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, C: Clone> Stream for TxProgress<T, C> {
|
||||
type Item = Result<TxStatus<T, C>, Error>;
|
||||
type Item = Result<TxStatus<T, C>, TransactionProgressError>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
@@ -132,37 +137,41 @@ impl<T: Config, C: Clone> Stream for TxProgress<T, C> {
|
||||
None => return Poll::Ready(None),
|
||||
};
|
||||
|
||||
sub.poll_next_unpin(cx).map_ok(|status| {
|
||||
match status {
|
||||
BackendTxStatus::Validated => TxStatus::Validated,
|
||||
BackendTxStatus::Broadcasted => TxStatus::Broadcasted,
|
||||
BackendTxStatus::NoLongerInBestBlock => TxStatus::NoLongerInBestBlock,
|
||||
BackendTxStatus::InBestBlock { hash } => {
|
||||
TxStatus::InBestBlock(TxInBlock::new(hash, self.ext_hash, self.client.clone()))
|
||||
}
|
||||
// These stream events mean that nothing further will be sent:
|
||||
BackendTxStatus::InFinalizedBlock { hash } => {
|
||||
self.sub = None;
|
||||
TxStatus::InFinalizedBlock(TxInBlock::new(
|
||||
sub.poll_next_unpin(cx)
|
||||
.map_err(TransactionProgressError::CannotGetNextProgressUpdate)
|
||||
.map_ok(|status| {
|
||||
match status {
|
||||
BackendTxStatus::Validated => TxStatus::Validated,
|
||||
BackendTxStatus::Broadcasted => TxStatus::Broadcasted,
|
||||
BackendTxStatus::NoLongerInBestBlock => TxStatus::NoLongerInBestBlock,
|
||||
BackendTxStatus::InBestBlock { hash } => TxStatus::InBestBlock(TxInBlock::new(
|
||||
hash,
|
||||
self.ext_hash,
|
||||
self.client.clone(),
|
||||
))
|
||||
)),
|
||||
// These stream events mean that nothing further will be sent:
|
||||
BackendTxStatus::InFinalizedBlock { hash } => {
|
||||
self.sub = None;
|
||||
TxStatus::InFinalizedBlock(TxInBlock::new(
|
||||
hash,
|
||||
self.ext_hash,
|
||||
self.client.clone(),
|
||||
))
|
||||
}
|
||||
BackendTxStatus::Error { message } => {
|
||||
self.sub = None;
|
||||
TxStatus::Error { message }
|
||||
}
|
||||
BackendTxStatus::Invalid { message } => {
|
||||
self.sub = None;
|
||||
TxStatus::Invalid { message }
|
||||
}
|
||||
BackendTxStatus::Dropped { message } => {
|
||||
self.sub = None;
|
||||
TxStatus::Dropped { message }
|
||||
}
|
||||
}
|
||||
BackendTxStatus::Error { message } => {
|
||||
self.sub = None;
|
||||
TxStatus::Error { message }
|
||||
}
|
||||
BackendTxStatus::Invalid { message } => {
|
||||
self.sub = None;
|
||||
TxStatus::Invalid { message }
|
||||
}
|
||||
BackendTxStatus::Dropped { message } => {
|
||||
self.sub = None;
|
||||
TxStatus::Dropped { message }
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,15 +267,27 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
|
||||
///
|
||||
/// **Note:** This has to download block details from the node and decode events
|
||||
/// from them.
|
||||
pub async fn wait_for_success(&self) -> Result<crate::blocks::ExtrinsicEvents<T>, Error> {
|
||||
pub async fn wait_for_success(
|
||||
&self,
|
||||
) -> Result<crate::blocks::ExtrinsicEvents<T>, TransactionEventsError> {
|
||||
let events = self.fetch_events().await?;
|
||||
|
||||
// Try to find any errors; return the first one we encounter.
|
||||
for ev in events.iter() {
|
||||
let ev = ev?;
|
||||
for (ev_idx, ev) in events.iter().enumerate() {
|
||||
let ev = ev.map_err(|e| TransactionEventsError::CannotDecodeEventInBlock {
|
||||
event_index: ev_idx,
|
||||
block_hash: self.block_hash().into(),
|
||||
error: e,
|
||||
})?;
|
||||
|
||||
if ev.pallet_name() == "System" && ev.variant_name() == "ExtrinsicFailed" {
|
||||
let dispatch_error =
|
||||
DispatchError::decode_from(ev.field_bytes(), self.client.metadata())?;
|
||||
DispatchError::decode_from(ev.field_bytes(), self.client.metadata()).map_err(
|
||||
|e| TransactionEventsError::CannotDecodeDispatchError {
|
||||
error: e,
|
||||
bytes: ev.field_bytes().to_vec(),
|
||||
},
|
||||
)?;
|
||||
return Err(dispatch_error.into());
|
||||
}
|
||||
}
|
||||
@@ -280,15 +301,23 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
|
||||
///
|
||||
/// **Note:** This has to download block details from the node and decode events
|
||||
/// from them.
|
||||
pub async fn fetch_events(&self) -> Result<crate::blocks::ExtrinsicEvents<T>, Error> {
|
||||
pub async fn fetch_events(
|
||||
&self,
|
||||
) -> Result<crate::blocks::ExtrinsicEvents<T>, TransactionEventsError> {
|
||||
let hasher = self.client.hasher();
|
||||
|
||||
let block_body = self
|
||||
.client
|
||||
.backend()
|
||||
.block_body(self.block_ref.hash())
|
||||
.await?
|
||||
.ok_or(Error::Transaction(TransactionError::BlockNotFound))?;
|
||||
.await
|
||||
.map_err(|e| TransactionEventsError::CannotFetchBlockBody {
|
||||
block_hash: self.block_hash().into(),
|
||||
error: e,
|
||||
})?
|
||||
.ok_or_else(|| TransactionEventsError::BlockNotFound {
|
||||
block_hash: self.block_hash().into(),
|
||||
})?;
|
||||
|
||||
let extrinsic_idx = block_body
|
||||
.iter()
|
||||
@@ -302,11 +331,21 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
|
||||
})
|
||||
// If we successfully obtain the block hash we think contains our
|
||||
// extrinsic, the extrinsic should be in there somewhere..
|
||||
.ok_or(Error::Transaction(TransactionError::BlockNotFound))?;
|
||||
.ok_or_else(|| TransactionEventsError::CannotFindTransactionInBlock {
|
||||
block_hash: self.block_hash().into(),
|
||||
transaction_hash: self.ext_hash.into(),
|
||||
})?;
|
||||
|
||||
let events = EventsClient::new(self.client.clone())
|
||||
.at(self.block_ref.clone())
|
||||
.await?;
|
||||
.await
|
||||
.map_err(
|
||||
|e| TransactionEventsError::CannotFetchEventsForTransaction {
|
||||
block_hash: self.block_hash().into(),
|
||||
transaction_hash: self.ext_hash.into(),
|
||||
error: e,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(crate::blocks::ExtrinsicEvents::new(
|
||||
self.ext_hash,
|
||||
@@ -318,10 +357,11 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use subxt_core::client::RuntimeVersion;
|
||||
|
||||
use crate::{
|
||||
Error, SubstrateConfig,
|
||||
SubstrateConfig,
|
||||
backend::{StreamOfResults, TransactionStatus},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, HashFor},
|
||||
@@ -375,7 +415,7 @@ mod test {
|
||||
let finalized_result = tx_progress.wait_for_finalized().await;
|
||||
assert!(matches!(
|
||||
finalized_result,
|
||||
Err(Error::Transaction(crate::error::TransactionError::Error(e))) if e == "err"
|
||||
Err(TransactionProgressError::TransactionStatusError(TransactionStatusError::Error(e))) if e == "err"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -390,7 +430,7 @@ mod test {
|
||||
let finalized_result = tx_progress.wait_for_finalized().await;
|
||||
assert!(matches!(
|
||||
finalized_result,
|
||||
Err(Error::Transaction(crate::error::TransactionError::Invalid(e))) if e == "err"
|
||||
Err(TransactionProgressError::TransactionStatusError(TransactionStatusError::Invalid(e))) if e == "err"
|
||||
));
|
||||
}
|
||||
|
||||
@@ -405,7 +445,7 @@ mod test {
|
||||
let finalized_result = tx_progress.wait_for_finalized().await;
|
||||
assert!(matches!(
|
||||
finalized_result,
|
||||
Err(Error::Transaction(crate::error::TransactionError::Dropped(e))) if e == "err"
|
||||
Err(TransactionProgressError::TransactionStatusError(TransactionStatusError::Dropped(e))) if e == "err"
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
mod view_function_types;
|
||||
mod view_functions_client;
|
||||
|
||||
pub use subxt_core::view_functions::payload::{
|
||||
DefaultPayload, DynamicPayload, Payload, StaticPayload, dynamic,
|
||||
};
|
||||
pub use subxt_core::view_functions::payload::{DynamicPayload, Payload, StaticPayload, dynamic};
|
||||
pub use view_function_types::ViewFunctionsApi;
|
||||
pub use view_functions_client::ViewFunctionsClient;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
backend::BlockRef,
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::ViewFunctionError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
@@ -40,7 +40,7 @@ where
|
||||
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
|
||||
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
|
||||
/// the View Function in question do not exist at all)
|
||||
pub fn validate<Call: Payload>(&self, payload: &Call) -> Result<(), Error> {
|
||||
pub fn validate<Call: Payload>(&self, payload: Call) -> Result<(), ViewFunctionError> {
|
||||
subxt_core::view_functions::validate(payload, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ where
|
||||
pub fn call<Call: Payload>(
|
||||
&self,
|
||||
payload: Call,
|
||||
) -> impl Future<Output = Result<Call::ReturnType, Error>> + use<Call, Client, T> {
|
||||
) -> impl Future<Output = Result<Call::ReturnType, ViewFunctionError>> + use<Call, Client, T>
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let block_hash = self.block_ref.hash();
|
||||
// Ensure that the returned future doesn't have a lifetime tied to api.view_functions(),
|
||||
@@ -68,7 +69,8 @@ where
|
||||
let bytes = client
|
||||
.backend()
|
||||
.call(call_name, Some(call_args.as_slice()), block_hash)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(ViewFunctionError::CannotCallApi)?;
|
||||
|
||||
// Decode the response.
|
||||
let value =
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
backend::BlockRef,
|
||||
client::OnlineClientT,
|
||||
config::{Config, HashFor},
|
||||
error::Error,
|
||||
error::ViewFunctionError,
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
@@ -43,13 +43,18 @@ where
|
||||
/// Obtain an interface to call View Functions at the latest finalized block.
|
||||
pub fn at_latest(
|
||||
&self,
|
||||
) -> impl Future<Output = Result<ViewFunctionsApi<T, Client>, Error>> + Send + 'static {
|
||||
) -> impl Future<Output = Result<ViewFunctionsApi<T, Client>, ViewFunctionError>> + Send + 'static
|
||||
{
|
||||
// Clone and pass the client in like this so that we can explicitly
|
||||
// return a Future that's Send + 'static, rather than tied to &self.
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
// get the ref for the latest finalized block and use that.
|
||||
let block_ref = client.backend().latest_finalized_block_ref().await?;
|
||||
let block_ref = client
|
||||
.backend()
|
||||
.latest_finalized_block_ref()
|
||||
.await
|
||||
.map_err(ViewFunctionError::CannotGetLatestFinalizedBlock)?;
|
||||
|
||||
Ok(ViewFunctionsApi::new(client, block_ref))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user