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:
James Wilson
2025-11-22 10:44:03 +00:00
committed by GitHub
parent 586b814ecd
commit 8203679cbd
158 changed files with 13736 additions and 16451 deletions
+11 -7
View File
@@ -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,
+24 -22
View File
@@ -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
View File
@@ -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
View File
@@ -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(&param), 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
View File
@@ -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(_))));
}
}
+33 -21
View File
@@ -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)
+42 -16
View File
@@ -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,
+32 -35
View File
@@ -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())
}
}
+21 -17
View File
@@ -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()).
//!
+8 -9
View File
@@ -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`].
//!
+26 -11
View File
@@ -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.
+5 -3
View File
@@ -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()
//! ()
//! );
//! ```
//!
+22 -70
View File
@@ -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()`].
//!
+1 -1
View File
@@ -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};
+82 -87
View File
@@ -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)
}
+11 -5
View File
@@ -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)
}
}
+1 -3
View File
@@ -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};
+16 -14
View File
@@ -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 {
+1 -1
View File
@@ -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};
+28 -16
View File
@@ -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:
+15
View File
@@ -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
View File
@@ -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,
}
}
}
+17 -9
View File
@@ -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)
}
+12 -11
View File
@@ -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)
}
+2 -2
View File
@@ -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
View File
@@ -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
+1 -3
View File
@@ -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};
+7 -3
View File
@@ -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))
}
+9 -6
View File
@@ -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)?;
+3 -5
View File
@@ -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};
+13 -23
View File
@@ -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))
}
}
}
+383
View File
@@ -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))
}
-323
View File
@@ -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
View File
@@ -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(&params),
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(&params),
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
View File
@@ -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"
));
}
+1 -3
View File
@@ -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))
}