Rework Subxt API to support offline and dynamic transactions (#593)

* WIP API changes

* debug impls

* Get main crate compiling with first round of changes

* Some tidy up

* Add WithExtrinsicParams, and have SubstrateConfig + PolkadotConfig, not DefaultConfig

* move transaction into extrinsic folder

* Add runtime updates back to OnlineClient

* rework to be 'client first' to fit better with storage + events

* add support for events to Client

* tidy dupe trait bound

* Wire storage into client, but need to remove static reliance

* various tidy up and start stripping codegen to remove bits we dont need now

* First pass updating calls and constants codegen

* WIP storage client updates

* First pass migrated runtime storage over to new format

* pass over codegen to generate StorageAddresses and throw other stuff out

* don't need a Call trait any more

* shuffle things around a bit

* Various proc_macro fixes to get 'cargo check' working

* organise what's exposed from subxt

* Get first example working; balance_transfer_with_params

* get balance_transfer example compiling

* get concurrent_storage_requests.rs example compiling

* get fetch_all_accounts example compiling

* get a bunch more of the examples compiling

* almost get final example working; type mismatch to look into

* wee tweaks

* move StorageAddress to separate file

* pass Defaultable/Iterable info to StorageAddress in codegen

* fix storage validation ne, and partial run through example code

* Remove static iteration and strip a generic param from everything

* fix doc tests in subxt crate

* update test utils and start fixing frame tests

* fix frame staking tests

* fix the rest of the test compile issues, Borrow on storage values

* cargo fmt

* remove extra logging during tests

* Appease clippy and no more need for into_iter on events

* cargo fmt

* fix dryRun tests by waiting for blocks

* wait for blocks instead of sleeping or other test hacks

* cargo fmt

* Fix doc links

* Traitify StorageAddress

* remove out-of-date doc comments

* optimise decoding storage a little

* cleanup tx stuff, trait for TxPayload, remove Err type param and decode at runtime

* clippy fixes

* fix doc links

* fix doc example

* constant address trait for consistency

* fix a typo and remove EncodeWithMetadata stuff

* Put EventDetails behind a proper interface and allow decoding into top level event, too

* fix docs

* tweak StorageAddress docs

* re-export StorageAddress at root for consistency

* fix clippy things

* Add support for dynamic values

* fix double encoding of storage map key after refactor

* clippy fix

* Fixes and add a dynamic usage example (needs new scale_value release)

* bump scale_value version

* cargo fmt

* Tweak event bits

* cargo fmt

* Add a test and bump scale-value to 0.4.0 to support this

* remove unnecessary vec from dynamic example

* Various typo/grammar fixes

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>

* Address PR nits

* Undo accidental rename in changelog

* Small PR nits/tidyups

* fix tests; codegen change against latest substrate

* tweak storage address util names

* move error decoding to DecodeError and expose

* impl some basic traits on the extrinsic param builder

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
James Wilson
2022-08-08 11:55:20 +01:00
committed by GitHub
parent 7a09ac6cd7
commit e48f0e3b1d
84 changed files with 23097 additions and 35863 deletions
-503
View File
@@ -1,503 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use futures::future;
pub use sp_runtime::traits::SignedExtension;
use sp_runtime::{
traits::Hash,
ApplyExtrinsicResult,
};
use crate::{
error::{
BasicError,
HasModuleError,
},
extrinsic::{
ExtrinsicParams,
Signer,
},
rpc::{
Rpc,
RpcClient,
RuntimeVersion,
SystemProperties,
},
storage::StorageClient,
transaction::TransactionProgress,
updates::UpdateClient,
Call,
Config,
Encoded,
Metadata,
};
use codec::{
Compact,
Decode,
Encode,
};
use derivative::Derivative;
use parking_lot::RwLock;
use std::sync::Arc;
/// ClientBuilder for constructing a Client.
#[derive(Default)]
pub struct ClientBuilder {
url: Option<String>,
client: Option<RpcClient>,
metadata: Option<Metadata>,
page_size: Option<u32>,
}
impl ClientBuilder {
/// Creates a new ClientBuilder.
pub fn new() -> Self {
Self {
url: None,
client: None,
metadata: None,
page_size: None,
}
}
/// Sets the jsonrpsee client.
pub fn set_client<C: Into<RpcClient>>(mut self, client: C) -> Self {
self.client = Some(client.into());
self
}
/// Set the substrate rpc address.
pub fn set_url<P: Into<String>>(mut self, url: P) -> Self {
self.url = Some(url.into());
self
}
/// Set the page size.
pub fn set_page_size(mut self, size: u32) -> Self {
self.page_size = Some(size);
self
}
/// Set the metadata.
///
/// *Note:* Metadata will no longer be downloaded from the runtime node.
#[cfg(feature = "integration-tests")]
pub fn set_metadata(mut self, metadata: Metadata) -> Self {
self.metadata = Some(metadata);
self
}
/// Builder for [Client].
///
/// # Examples
///
/// ```no_run
/// use subxt::{ClientBuilder, DefaultConfig};
///
/// #[tokio::main]
/// async fn main() {
/// // Build the client.
/// let client = ClientBuilder::new()
/// .set_url("wss://rpc.polkadot.io:443")
/// .build::<DefaultConfig>()
/// .await
/// .unwrap();
/// // Use the client...
/// }
/// ```
pub async fn build<T: Config>(self) -> Result<Client<T>, BasicError> {
let client = if let Some(client) = self.client {
client
} else {
let url = self.url.as_deref().unwrap_or("ws://127.0.0.1:9944");
crate::rpc::ws_client(url).await?
};
let rpc = Rpc::new(client);
let (genesis_hash, runtime_version, properties) = future::join3(
rpc.genesis_hash(),
rpc.runtime_version(None),
rpc.system_properties(),
)
.await;
let metadata = if let Some(metadata) = self.metadata {
metadata
} else {
rpc.metadata().await?
};
Ok(Client {
rpc,
genesis_hash: genesis_hash?,
metadata: Arc::new(RwLock::new(metadata)),
properties: properties.unwrap_or_else(|_| Default::default()),
runtime_version: Arc::new(RwLock::new(runtime_version?)),
iter_page_size: self.page_size.unwrap_or(10),
})
}
}
/// Client to interface with a substrate node.
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Client<T: Config> {
rpc: Rpc<T>,
genesis_hash: T::Hash,
metadata: Arc<RwLock<Metadata>>,
properties: SystemProperties,
runtime_version: Arc<RwLock<RuntimeVersion>>,
iter_page_size: u32,
}
impl<T: Config> std::fmt::Debug for Client<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("rpc", &"<Rpc>")
.field("genesis_hash", &self.genesis_hash)
.field("metadata", &"<Metadata>")
.field("events_decoder", &"<EventsDecoder>")
.field("properties", &self.properties)
.field("runtime_version", &self.runtime_version)
.field("iter_page_size", &self.iter_page_size)
.finish()
}
}
impl<T: Config> Client<T> {
/// Returns the genesis hash.
pub fn genesis(&self) -> &T::Hash {
&self.genesis_hash
}
/// Returns the chain metadata.
pub fn metadata(&self) -> Arc<RwLock<Metadata>> {
Arc::clone(&self.metadata)
}
/// Returns the properties defined in the chain spec as a JSON object.
///
/// # Note
///
/// Many chains use this to define common properties such as `token_decimals` and `token_symbol`
/// required for UIs, but this is merely a convention. It is up to the library user to
/// deserialize the JSON into the appropriate type or otherwise extract the properties defined
/// in the target chain's spec.
pub fn properties(&self) -> &SystemProperties {
&self.properties
}
/// Returns the rpc client.
pub fn rpc(&self) -> &Rpc<T> {
&self.rpc
}
/// Create a client for accessing runtime storage
pub fn storage(&self) -> StorageClient<T> {
StorageClient::new(&self.rpc, self.metadata(), self.iter_page_size)
}
/// Create a wrapper for performing runtime updates on this client.
///
/// # Note
///
/// The update client is intended to be used in the background for
/// performing runtime updates, while the API is still in use.
/// Without performing runtime updates the submitted extrinsics may fail.
///
/// # Examples
///
/// ```no_run
/// # use subxt::{ClientBuilder, DefaultConfig};
/// #
/// # #[tokio::main]
/// # async fn main() {
/// # let client = ClientBuilder::new()
/// # .set_url("wss://rpc.polkadot.io:443")
/// # .build::<DefaultConfig>()
/// # .await
/// # .unwrap();
/// #
/// let update_client = client.updates();
/// // Spawn a new background task to handle runtime updates.
/// tokio::spawn(async move {
/// let result = update_client.perform_runtime_updates().await;
/// println!("Runtime update finished with result={:?}", result);
/// });
/// # }
/// ```
pub fn updates(&self) -> UpdateClient<T> {
UpdateClient::new(
self.rpc.clone(),
self.metadata(),
self.runtime_version.clone(),
)
}
/// Convert the client to a runtime api wrapper for custom runtime access.
///
/// The `subxt` proc macro will provide methods to submit extrinsics and read storage specific
/// to the target runtime.
pub fn to_runtime_api<R: From<Self>>(self) -> R {
self.into()
}
/// Returns the client's Runtime Version.
pub fn runtime_version(&self) -> Arc<RwLock<RuntimeVersion>> {
Arc::clone(&self.runtime_version)
}
}
/// A constructed call ready to be signed and submitted.
pub struct SubmittableExtrinsic<'client, T: Config, X, C, E: Decode, Evs: Decode> {
client: &'client Client<T>,
call: C,
marker: std::marker::PhantomData<(X, E, Evs)>,
}
impl<'client, T, X, C, E, Evs> SubmittableExtrinsic<'client, T, X, C, E, Evs>
where
T: Config,
X: ExtrinsicParams<T>,
C: Call + Send + Sync,
E: Decode + HasModuleError,
Evs: Decode,
{
/// Create a new [`SubmittableExtrinsic`].
pub fn new(client: &'client Client<T>, call: C) -> Self {
Self {
client,
call,
marker: Default::default(),
}
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
///
/// Returns a [`TransactionProgress`], 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 sign_and_submit_then_watch_default(
&self,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError>
where
X::OtherParams: Default,
{
self.sign_and_submit_then_watch(signer, Default::default())
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
///
/// Returns a [`TransactionProgress`], 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 sign_and_submit_then_watch(
&self,
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError> {
self.create_signed(signer, other_params)
.await?
.submit_and_watch()
.await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default(
&self,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<T::Hash, BasicError>
where
X::OtherParams: Default,
{
self.sign_and_submit(signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit(
&self,
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<T::Hash, BasicError> {
self.create_signed(signer, other_params)
.await?
.submit()
.await
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data(&self) -> Result<Vec<u8>, BasicError> {
let mut bytes = Vec::new();
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
let pallet = metadata.pallet(C::PALLET)?;
bytes.push(pallet.index());
bytes.push(pallet.call_index::<C>()?);
self.call.encode_to(&mut bytes);
Ok(bytes)
}
/// Creates a returns a raw signed extrinsic, without submitting it.
pub async fn create_signed(
&self,
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<SignedSubmittableExtrinsic<'client, T, X, E, Evs>, BasicError> {
// 1. Get nonce
let account_nonce = if let Some(nonce) = signer.nonce() {
nonce
} else {
self.client
.rpc()
.system_account_next_index(signer.account_id())
.await?
};
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = Encoded(self.call_data()?);
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = {
// Obtain spec version and transaction version from the runtime version of the client.
let locked_runtime = self.client.runtime_version();
let runtime = locked_runtime.read();
X::new(
runtime.spec_version,
runtime.transaction_version,
account_nonce,
self.client.genesis_hash,
other_params,
)
};
tracing::debug!(
"additional_and_extra_params: {:?}",
additional_and_extra_params
);
// 4. Construct signature. This is compatible with the Encode impl
// for SignedPayload (which is this payload of bytes that we'd like)
// to sign. See:
// https://github.com/paritytech/substrate/blob/9a6d706d8db00abb6ba183839ec98ecd9924b1f8/primitives/runtime/src/generic/unchecked_extrinsic.rs#L215)
let signature = {
let mut bytes = Vec::new();
call_data.encode_to(&mut bytes);
additional_and_extra_params.encode_extra_to(&mut bytes);
additional_and_extra_params.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
signer.sign(&sp_core::blake2_256(&bytes))
} else {
signer.sign(&bytes)
}
};
tracing::info!("xt signature: {}", hex::encode(signature.encode()));
// 5. Encode extrinsic, now that we have the parts we need. This is compatible
// with the Encode impl for UncheckedExtrinsic (protocol version 4).
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
signer.address().encode_to(&mut encoded_inner);
// the signature bytes
signature.encode_to(&mut encoded_inner);
// attach custom extra params
additional_and_extra_params.encode_extra_to(&mut encoded_inner);
// and now, call data
call_data.encode_to(&mut encoded_inner);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
.expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
// maybe we can just return the raw bytes..
Ok(SignedSubmittableExtrinsic {
client: self.client,
encoded: Encoded(extrinsic),
marker: self.marker,
})
}
}
pub struct SignedSubmittableExtrinsic<'client, T: Config, X, E: Decode, Evs: Decode> {
client: &'client Client<T>,
encoded: Encoded,
marker: std::marker::PhantomData<(X, E, Evs)>,
}
impl<'client, T, X, E, Evs> SignedSubmittableExtrinsic<'client, T, X, E, Evs>
where
T: Config,
X: ExtrinsicParams<T>,
E: Decode + HasModuleError,
Evs: Decode,
{
/// Submits the extrinsic to the chain.
///
/// Returns a [`TransactionProgress`], 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<TransactionProgress<'client, T, E, Evs>, BasicError> {
// Get a hash of the extrinsic (we'll need this later).
let ext_hash = T::Hashing::hash_of(&self.encoded);
// Submit and watch for transaction progress.
let sub = self.client.rpc().watch_extrinsic(&self.encoded).await?;
Ok(TransactionProgress::new(sub, self.client, ext_hash))
}
/// Submits the extrinsic to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn submit(&self) -> Result<T::Hash, BasicError> {
self.client.rpc().submit_extrinsic(&self.encoded).await
}
/// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
///
/// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
pub async fn dry_run(
&self,
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, BasicError> {
self.client.rpc().dry_run(self.encoded(), at).await
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module provides two clients that can be used to work with
//! transactions, storage and events. The [`OfflineClient`] works
//! entirely offline and can be passed to any function that doesn't
//! require network access. The [`OnlineClient`] requires network
//! access.
mod offline_client;
mod online_client;
pub use offline_client::{
OfflineClient,
OfflineClientT,
};
pub use online_client::{
OnlineClient,
OnlineClientT,
};
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
constants::ConstantsClient,
events::EventsClient,
rpc::RuntimeVersion,
storage::StorageClient,
tx::TxClient,
Config,
Metadata,
};
use derivative::Derivative;
use std::sync::Arc;
/// A trait representing a client that can perform
/// offline-only actions.
pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
/// Return the provided [`Metadata`].
fn metadata(&self) -> Metadata;
/// Return the provided genesis hash.
fn genesis_hash(&self) -> T::Hash;
/// Return the provided [`RuntimeVersion`].
fn runtime_version(&self) -> RuntimeVersion;
/// Work with transactions.
fn tx(&self) -> TxClient<T, Self> {
TxClient::new(self.clone())
}
/// Work with events.
fn events(&self) -> EventsClient<T, Self> {
EventsClient::new(self.clone())
}
/// Work with storage.
fn storage(&self) -> StorageClient<T, Self> {
StorageClient::new(self.clone())
}
/// Access constants.
fn constants(&self) -> ConstantsClient<T, Self> {
ConstantsClient::new(self.clone())
}
}
/// A client that is capable of performing offline-only operations.
/// Can be constructed as long as you can populate the required fields.
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
pub struct OfflineClient<T: Config> {
inner: Arc<Inner<T>>,
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
struct Inner<T: Config> {
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<T: Config> OfflineClient<T> {
/// Construct a new [`OfflineClient`], providing
/// the necessary runtime and compile-time arguments.
pub fn new(
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
) -> OfflineClient<T> {
OfflineClient {
inner: Arc::new(Inner {
genesis_hash,
runtime_version,
metadata,
}),
}
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
self.inner.genesis_hash
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
self.inner.runtime_version.clone()
}
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
self.inner.metadata.clone()
}
// Just a copy of the most important trait methods so that people
// don't need to import the trait for most things:
/// Work with transactions.
pub fn tx(&self) -> TxClient<T, Self> {
<Self as OfflineClientT<T>>::tx(self)
}
/// Work with events.
pub fn events(&self) -> EventsClient<T, Self> {
<Self as OfflineClientT<T>>::events(self)
}
/// Work with storage.
pub fn storage(&self) -> StorageClient<T, Self> {
<Self as OfflineClientT<T>>::storage(self)
}
/// Access constants.
pub fn constants(&self) -> ConstantsClient<T, Self> {
<Self as OfflineClientT<T>>::constants(self)
}
}
impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
fn genesis_hash(&self) -> T::Hash {
self.genesis_hash()
}
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
fn metadata(&self) -> Metadata {
self.metadata()
}
}
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
// so this allows users to pass references to a client rather than explicitly
// cloning. This is partly for consistency with OnlineClient, which can be
// easily converted into an OfflineClient for ergonomics.
impl<'a, T: Config> From<&'a OfflineClient<T>> for OfflineClient<T> {
fn from(c: &'a OfflineClient<T>) -> Self {
c.clone()
}
}
+234
View File
@@ -0,0 +1,234 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{
OfflineClient,
OfflineClientT,
};
use crate::{
constants::ConstantsClient,
error::Error,
events::EventsClient,
rpc::{
Rpc,
RpcClient,
RuntimeVersion,
},
storage::StorageClient,
tx::TxClient,
Config,
Metadata,
};
use derivative::Derivative;
use futures::future;
use parking_lot::RwLock;
use std::sync::Arc;
/// A trait representing a client that can perform
/// online actions.
pub trait OnlineClientT<T: Config>: OfflineClientT<T> {
/// Return an RPC client that can be used to communicate with a node.
fn rpc(&self) -> &Rpc<T>;
}
/// A client that can be used to perform API calls (that is, either those
/// requiriing an [`OfflineClientT`] or those requiring an [`OnlineClientT`]).
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct OnlineClient<T: Config> {
inner: Arc<RwLock<Inner<T>>>,
rpc: Rpc<T>,
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct Inner<T: Config> {
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<T: Config> std::fmt::Debug for OnlineClient<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("rpc", &"<Rpc>")
.field("inner", &self.inner)
.finish()
}
}
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> {
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> {
let client = crate::rpc::ws_client(url.as_ref()).await?;
OnlineClient::from_rpc_client(client).await
}
/// Construct a new [`OnlineClient`] by providing the underlying [`RpcClient`]
/// to use to drive the connection.
pub async fn from_rpc_client(
rpc_client: impl Into<RpcClient>,
) -> Result<OnlineClient<T>, Error> {
let rpc = Rpc::new(rpc_client.into());
let (genesis_hash, runtime_version, metadata) = future::join3(
rpc.genesis_hash(),
rpc.runtime_version(None),
rpc.metadata(),
)
.await;
Ok(OnlineClient {
inner: Arc::new(RwLock::new(Inner {
genesis_hash: genesis_hash?,
runtime_version: runtime_version?,
metadata: metadata?,
})),
rpc,
})
}
/// Create an object which can be used to keep the runtime uptodate
/// in a separate thread.
///
/// # Example
///
/// ```no_run
/// # #[tokio::main]
/// # async fn main() {
/// use subxt::{ OnlineClient, PolkadotConfig };
///
/// let client = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// let update_task = client.subscribe_to_updates();
/// tokio::spawn(async move {
/// update_task.perform_runtime_updates().await;
/// });
/// # }
/// ```
pub fn subscribe_to_updates(&self) -> ClientRuntimeUpdater<T> {
ClientRuntimeUpdater(self.clone())
}
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
let inner = self.inner.read();
inner.metadata.clone()
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
let inner = self.inner.read();
inner.genesis_hash
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
let inner = self.inner.read();
inner.runtime_version.clone()
}
/// Return an RPC client to make raw requests with.
pub fn rpc(&self) -> &Rpc<T> {
&self.rpc
}
/// Return an offline client with the same configuration as this.
pub fn offline(&self) -> OfflineClient<T> {
let inner = self.inner.read();
OfflineClient::new(
inner.genesis_hash,
inner.runtime_version.clone(),
inner.metadata.clone(),
)
}
// Just a copy of the most important trait methods so that people
// don't need to import the trait for most things:
/// Work with transactions.
pub fn tx(&self) -> TxClient<T, Self> {
<Self as OfflineClientT<T>>::tx(self)
}
/// Work with events.
pub fn events(&self) -> EventsClient<T, Self> {
<Self as OfflineClientT<T>>::events(self)
}
/// Work with storage.
pub fn storage(&self) -> StorageClient<T, Self> {
<Self as OfflineClientT<T>>::storage(self)
}
/// Access constants.
pub fn constants(&self) -> ConstantsClient<T, Self> {
<Self as OfflineClientT<T>>::constants(self)
}
}
impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
fn metadata(&self) -> Metadata {
self.metadata()
}
fn genesis_hash(&self) -> T::Hash {
self.genesis_hash()
}
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
}
impl<T: Config> OnlineClientT<T> for OnlineClient<T> {
fn rpc(&self) -> &Rpc<T> {
&self.rpc
}
}
/// Client wrapper for performing runtime updates. See [`OnlineClient::subscribe_to_updates()`]
/// for example usage.
pub struct ClientRuntimeUpdater<T: Config>(OnlineClient<T>);
impl<T: Config> ClientRuntimeUpdater<T> {
fn is_runtime_version_different(&self, new: &RuntimeVersion) -> bool {
let curr = self.0.inner.read();
&curr.runtime_version != new
}
/// Performs runtime updates indefinitely unless encountering an error.
///
/// *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> {
// Obtain an update subscription to further detect changes in the runtime version of the node.
let mut update_subscription = self.0.rpc.subscribe_runtime_version().await?;
while let Some(new_runtime_version) = update_subscription.next().await {
// The Runtime Version obtained via subscription.
let new_runtime_version = new_runtime_version?;
// Ignore this update if there is no difference.
if !self.is_runtime_version_different(&new_runtime_version) {
continue
}
// Fetch new metadata.
let new_metadata = self.0.rpc.metadata().await?;
// Do the update.
let mut writable = self.0.inner.write();
writable.metadata = new_metadata;
writable.runtime_version = new_runtime_version;
}
Ok(())
}
}
+54 -4
View File
@@ -2,6 +2,12 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module provides a [`Config`] type, which is used to define various
//! types that are important in order to speak to a particular chain.
//! [`SubstrateConfig`] provides a default set of these types suitable for the
//! default Substrate node implementation, and [`PolkadotConfig`] for a
//! Polkadot node.
use codec::{
Codec,
Encode,
@@ -22,7 +28,7 @@ use sp_runtime::traits::{
// Note: the 'static bound isn't strictly required, but currently deriving TypeInfo
// automatically applies a 'static bound to all generic types (including this one),
// and so until that is resolved, we'll keep the (easy to satisfy) constraint here.
pub trait Config: Debug + 'static {
pub trait Config: 'static {
/// Account index (aka nonce) type. This stores the number of previous
/// transactions associated with a sender account.
type Index: Parameter
@@ -74,6 +80,9 @@ pub trait Config: Debug + 'static {
/// Extrinsic type within blocks.
type Extrinsic: Parameter + Extrinsic + Debug + MaybeSerializeDeserialize;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: crate::tx::ExtrinsicParams<Self::Index, Self::Hash>;
}
/// Parameter trait copied from `substrate::frame_support`
@@ -83,10 +92,9 @@ impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug {}
/// Default set of commonly used types by Substrate runtimes.
// Note: We only use this at the type level, so it should be impossible to
// create an instance of it.
#[derive(Debug)]
pub enum DefaultConfig {}
pub enum SubstrateConfig {}
impl Config for DefaultConfig {
impl Config for SubstrateConfig {
type Index = u32;
type BlockNumber = u32;
type Hash = sp_core::H256;
@@ -97,4 +105,46 @@ impl Config for DefaultConfig {
sp_runtime::generic::Header<Self::BlockNumber, sp_runtime::traits::BlakeTwo256>;
type Signature = sp_runtime::MultiSignature;
type Extrinsic = sp_runtime::OpaqueExtrinsic;
type ExtrinsicParams = crate::tx::SubstrateExtrinsicParams<Self>;
}
/// Default set of commonly used types by Polkadot nodes.
pub type PolkadotConfig = WithExtrinsicParams<
SubstrateConfig,
crate::tx::PolkadotExtrinsicParams<SubstrateConfig>,
>;
/// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the
/// additional and extra parameters to pass to an extrinsic (see [`crate::tx::ExtrinsicParams`]),
/// and returns a type implementing [`Config`] with those new `ExtrinsicParams`.
///
/// # Example
///
/// ```
/// use subxt::config::{ SubstrateConfig, WithExtrinsicParams };
/// use subxt::tx::PolkadotExtrinsicParams;
///
/// // This is how PolkadotConfig is implemented:
/// type PolkadotConfig = WithExtrinsicParams<SubstrateConfig, PolkadotExtrinsicParams<SubstrateConfig>>;
/// ```
pub struct WithExtrinsicParams<
T: Config,
E: crate::tx::ExtrinsicParams<T::Index, T::Hash>,
> {
_marker: std::marker::PhantomData<(T, E)>,
}
impl<T: Config, E: crate::tx::ExtrinsicParams<T::Index, T::Hash>> Config
for WithExtrinsicParams<T, E>
{
type Index = T::Index;
type BlockNumber = T::BlockNumber;
type Hash = T::Hash;
type Hashing = T::Hashing;
type AccountId = T::AccountId;
type Address = T::Address;
type Header = T::Header;
type Signature = T::Signature;
type Extrinsic = T::Extrinsic;
type ExtrinsicParams = E;
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::DecodedValue,
metadata::DecodeWithMetadata,
};
use std::borrow::Cow;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait ConstantAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
/// The name of the constant in a given pallet.
fn constant_name(&self) -> &str;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents a statically generated constant lookup address.
pub struct StaticConstantAddress<ReturnTy> {
pallet_name: &'static str,
constant_name: &'static str,
constant_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<ReturnTy>,
}
impl<ReturnTy> StaticConstantAddress<ReturnTy> {
/// Create a new [`StaticConstantAddress`] that will be validated
/// against node metadata using the hash given.
pub fn new(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name,
constant_name,
constant_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this constant prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
pallet_name: self.pallet_name,
constant_name: self.constant_name,
constant_hash: None,
_marker: self._marker,
}
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for StaticConstantAddress<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn constant_name(&self) -> &str {
self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// This represents a dynamically generated constant address.
pub struct DynamicConstantAddress<'a> {
pallet_name: Cow<'a, str>,
constant_name: Cow<'a, str>,
}
/// Construct a new dynamic constant lookup.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
constant_name: impl Into<Cow<'a, str>>,
) -> DynamicConstantAddress<'a> {
DynamicConstantAddress {
pallet_name: pallet_name.into(),
constant_name: constant_name.into(),
}
}
impl<'a> ConstantAddress for DynamicConstantAddress<'a> {
type Target = DecodedValue;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn constant_name(&self) -> &str {
&self.constant_name
}
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::ConstantAddress;
use crate::{
client::OfflineClientT,
error::Error,
metadata::{
DecodeWithMetadata,
MetadataError,
},
Config,
};
use derivative::Derivative;
/// A client for accessing constants.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct ConstantsClient<T, Client> {
client: Client,
_marker: std::marker::PhantomData<T>,
}
impl<T, Client> ConstantsClient<T, Client> {
/// Create a new [`ConstantsClient`].
pub fn new(client: Client) -> Self {
Self {
client,
_marker: std::marker::PhantomData,
}
}
}
impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
/// Run the validation logic against some constant 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).
/// 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<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = self
.client
.metadata()
.constant_hash(address.pallet_name(), address.constant_name())?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleMetadata.into())
}
}
Ok(())
}
/// 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<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<<Address::Target as DecodeWithMetadata>::Target, Error> {
let metadata = self.client.metadata();
// 1. Validate constant shape if hash given:
self.validate(address)?;
// 2. Attempt to decode the constant into the type given:
let pallet = metadata.pallet(address.pallet_name())?;
let constant = pallet.constant(address.constant_name())?;
let value = Address::Target::decode_with_metadata(
&mut &*constant.value,
constant.ty.id(),
&metadata,
)?;
Ok(value)
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing constants.
mod constant_address;
mod constants_client;
pub use constant_address::{
dynamic,
ConstantAddress,
DynamicConstantAddress,
StaticConstantAddress,
};
pub use constants_client::ConstantsClient;
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module provides the entry points to create dynamic
//! transactions, storage and constant lookups.
pub use scale_value::Value;
/// A [`scale_value::Value`] type endowed with contextual information
/// regarding what type was used to decode each part of it. This implements
/// [`crate::metadata::DecodeWithMetadata`], and is used as a return type
/// for dynamic requests.
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
// Submit dynamic transactions.
pub use crate::tx::dynamic as tx;
// Lookup constants dynamically.
pub use crate::constants::dynamic as constant;
// Lookup storage values dynamically.
pub use crate::storage::{
dynamic as storage,
dynamic_root as storage_root,
};
+191 -89
View File
@@ -2,27 +2,32 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::metadata::{
//! Types representing the errors that can be returned.
use crate::metadata::Metadata;
use codec::Decode;
use core::fmt::Debug;
use scale_info::TypeDef;
use std::borrow::Cow;
// Re-expose the errors we use from other crates here:
pub use crate::metadata::{
InvalidMetadataError,
MetadataError,
};
use core::fmt::Debug;
use jsonrpsee::core::error::Error as RequestError;
use scale_value::scale::DecodeError;
use sp_core::crypto::SecretStringError;
use sp_runtime::transaction_validity::TransactionValidityError;
/// An error that may contain some runtime error `E`
pub type Error<E> = GenericError<RuntimeError<E>>;
/// An error that will never contain a runtime error.
pub type BasicError = GenericError<std::convert::Infallible>;
pub use jsonrpsee::core::error::Error as RequestError;
pub use scale_value::scale::{
DecodeError,
EncodeError,
};
pub use sp_core::crypto::SecretStringError;
pub use sp_runtime::transaction_validity::TransactionValidityError;
/// The underlying error enum, generic over the type held by the `Runtime`
/// variant. Prefer to use the [`Error<E>`] and [`BasicError`] aliases over
/// variant. Prefer to use the [`Error<E>`] and [`Error`] aliases over
/// using this type directly.
#[derive(Debug, thiserror::Error)]
pub enum GenericError<E> {
pub enum Error {
/// Io error.
#[error("Io error: {0}")]
Io(#[from] std::io::Error),
@@ -49,99 +54,174 @@ pub enum GenericError<E> {
Metadata(#[from] MetadataError),
/// Runtime error.
#[error("Runtime error: {0:?}")]
Runtime(E),
/// Events decoding error.
#[error("Events decoding error: {0}")]
EventsDecoding(#[from] DecodeError),
Runtime(DispatchError),
/// Error decoding to a [`crate::dynamic::Value`].
#[error("Error decoding into dynamic value: {0}")]
DecodeValue(#[from] DecodeError),
/// Error encoding from a [`crate::dynamic::Value`].
#[error("Error encoding from dynamic value: {0}")]
EncodeValue(#[from] EncodeError<()>),
/// Transaction progress error.
#[error("Transaction error: {0}")]
Transaction(#[from] TransactionError),
#[error("Module error: {0}")]
/// An error from the `Module` variant of the generated `DispatchError`.
Module(ModuleError),
/// An error encoding a storage address.
#[error("Error encoding storage address: {0}")]
StorageAddress(#[from] StorageAddressError),
/// Other error.
#[error("Other error: {0}")]
Other(String),
}
impl<E> GenericError<E> {
/// [`GenericError`] is parameterised over the type that it holds in the `Runtime`
/// variant. This function allows us to map the Runtime error contained within (if present)
/// to a different type.
pub fn map_runtime_err<F, NewE>(self, f: F) -> GenericError<NewE>
where
F: FnOnce(E) -> NewE,
{
match self {
GenericError::Io(e) => GenericError::Io(e),
GenericError::Codec(e) => GenericError::Codec(e),
GenericError::Rpc(e) => GenericError::Rpc(e),
GenericError::Serialization(e) => GenericError::Serialization(e),
GenericError::SecretString(e) => GenericError::SecretString(e),
GenericError::Invalid(e) => GenericError::Invalid(e),
GenericError::InvalidMetadata(e) => GenericError::InvalidMetadata(e),
GenericError::Metadata(e) => GenericError::Metadata(e),
GenericError::EventsDecoding(e) => GenericError::EventsDecoding(e),
GenericError::Transaction(e) => GenericError::Transaction(e),
GenericError::Module(e) => GenericError::Module(e),
GenericError::Other(e) => GenericError::Other(e),
// This is the only branch we really care about:
GenericError::Runtime(e) => GenericError::Runtime(f(e)),
}
}
}
impl BasicError {
/// Convert an [`BasicError`] into any
/// arbitrary [`Error<E>`].
pub fn into_error<E>(self) -> Error<E> {
self.map_runtime_err(|e| match e {})
}
}
impl<E> From<BasicError> for Error<E> {
fn from(err: BasicError) -> Self {
err.into_error()
}
}
impl<E> From<SecretStringError> for GenericError<E> {
impl From<SecretStringError> for Error {
fn from(error: SecretStringError) -> Self {
GenericError::SecretString(error)
Error::SecretString(error)
}
}
impl<E> From<TransactionValidityError> for GenericError<E> {
impl From<TransactionValidityError> for Error {
fn from(error: TransactionValidityError) -> Self {
GenericError::Invalid(error)
Error::Invalid(error)
}
}
impl<E> From<&str> for GenericError<E> {
impl From<&str> for Error {
fn from(error: &str) -> Self {
GenericError::Other(error.into())
Error::Other(error.into())
}
}
impl<E> From<String> for GenericError<E> {
impl From<String> for Error {
fn from(error: String) -> Self {
GenericError::Other(error)
Error::Other(error)
}
}
/// This is used in the place of the `E` in [`GenericError<E>`] when we may have a
/// Runtime Error. We use this wrapper so that it is possible to implement
/// `From<Error<Infallible>` for `Error<RuntimeError<E>>`.
///
/// This should not be used as a type; prefer to use the alias [`Error<E>`] when referring
/// to errors which may contain some Runtime error `E`.
#[derive(Clone, Debug, PartialEq)]
pub struct RuntimeError<E>(pub E);
impl From<DispatchError> for Error {
fn from(error: DispatchError) -> Self {
Error::Runtime(error)
}
}
impl<E> RuntimeError<E> {
/// Extract the actual runtime error from this struct.
pub fn inner(self) -> E {
self.0
/// This is our attempt to decode a runtime DispatchError. We either
/// successfully decode it into a [`ModuleError`], or we fail and keep
/// hold of the bytes, which we can attempt to decode if we have an
/// appropriate static type to hand.
#[derive(Debug, thiserror::Error)]
pub enum DispatchError {
/// An error was emitted from a specific pallet/module.
#[error("Module error: {0}")]
Module(ModuleError),
/// Some other error was emitted.
#[error("Undecoded dispatch error: {0:?}")]
Other(Vec<u8>),
}
impl DispatchError {
/// Attempt to decode a runtime DispatchError, returning either the [`ModuleError`] it decodes
/// to, along with additional details on the error, or returning the raw bytes if it could not
/// be decoded.
pub fn decode_from<'a>(bytes: impl Into<Cow<'a, [u8]>>, metadata: &Metadata) -> Self {
let bytes = bytes.into();
let dispatch_error_ty_id = match metadata.dispatch_error_ty() {
Some(id) => id,
None => {
tracing::warn!(
"Can't decode error: sp_runtime::DispatchError was not found in Metadata"
);
return DispatchError::Other(bytes.into_owned())
}
};
let dispatch_error_ty = match metadata.types().resolve(dispatch_error_ty_id) {
Some(ty) => ty,
None => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError type ID doesn't resolve to a known type");
return DispatchError::Other(bytes.into_owned())
}
};
let variant = match dispatch_error_ty.type_def() {
TypeDef::Variant(var) => var,
_ => {
tracing::warn!(
"Can't decode error: sp_runtime::DispatchError type is not a Variant"
);
return DispatchError::Other(bytes.into_owned())
}
};
let module_variant_idx = variant
.variants()
.iter()
.find(|v| v.name() == "Module")
.map(|v| v.index());
let module_variant_idx = match module_variant_idx {
Some(idx) => idx,
None => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError does not have a 'Module' variant");
return DispatchError::Other(bytes.into_owned())
}
};
// If the error bytes don't correspond to a ModuleError, just return the bytes.
// This is perfectly reasonable and expected, so no logging.
if bytes[0] != module_variant_idx {
return DispatchError::Other(bytes.into_owned())
}
// The remaining bytes are the module error, all being well:
let bytes = &bytes[1..];
// The oldest and second oldest type of error decode to this shape:
#[derive(Decode)]
struct LegacyModuleError {
index: u8,
error: u8,
}
// The newer case expands the error for forward compat:
#[derive(Decode)]
struct CurrentModuleError {
index: u8,
error: [u8; 4],
}
// try to decode into the new shape, or the old if that doesn't work
let err = match CurrentModuleError::decode(&mut &*bytes) {
Ok(e) => e,
Err(_) => {
let old_e = match LegacyModuleError::decode(&mut &*bytes) {
Ok(err) => err,
Err(_) => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError does not match known formats");
return DispatchError::Other(bytes.to_vec())
}
};
CurrentModuleError {
index: old_e.index,
error: [old_e.error, 0, 0, 0],
}
}
};
let error_details = match metadata.error(err.index, err.error[0]) {
Ok(details) => details,
Err(_) => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError::Module details do not match known information");
return DispatchError::Other(bytes.to_vec())
}
};
DispatchError::Module(ModuleError {
pallet: error_details.pallet().to_string(),
error: error_details.error().to_string(),
description: error_details.docs().to_vec(),
error_data: ModuleErrorData {
pallet_index: err.index,
error: err.error,
},
})
}
}
@@ -192,11 +272,33 @@ impl ModuleErrorData {
}
}
/// This trait is automatically implemented for the generated `DispatchError`,
/// so that we can pluck out information about the `Module` error variant, if`
/// it exists.
pub trait HasModuleError {
/// If the error has a `Module` variant, return a tuple of the
/// pallet index and error index. Else, return `None`.
fn module_error_data(&self) -> Option<ModuleErrorData>;
/// Something went wrong trying to encode a storage address.
#[derive(Clone, Debug, thiserror::Error)]
pub enum StorageAddressError {
/// Storage map type must be a composite type.
#[error("Storage map type must be a composite type")]
MapTypeMustBeTuple,
/// Storage lookup does not have the expected number of keys.
#[error("Storage lookup requires {expected} keys but got {actual} keys")]
WrongNumberOfKeys {
/// The actual number of keys needed, based on the metadata.
actual: usize,
/// The number of keys provided in the storage address.
expected: usize,
},
/// Storage lookup requires a type that wasn't found in the metadata.
#[error(
"Storage lookup requires type {0} to exist in the metadata, but it was not found"
)]
TypeNotFound(u32),
/// This storage entry in the metadata does not have the correct number of hashers to fields.
#[error(
"Storage entry in metadata does not have the correct number of hashers to fields"
)]
WrongNumberOfHashers {
/// The number of hashers in the metadata for this storage entry.
hashers: usize,
/// The number of fields in the metadata for this storage entry.
fields: usize,
},
}
+65 -135
View File
@@ -5,18 +5,14 @@
//! Subscribing to events.
use crate::{
error::BasicError,
Client,
client::OnlineClientT,
error::Error,
events::EventsClient,
Config,
};
use codec::Decode;
use derivative::Derivative;
use futures::{
future::Either,
stream::{
self,
BoxStream,
},
stream::BoxStream,
Future,
FutureExt,
Stream,
@@ -30,112 +26,16 @@ use std::{
};
pub use super::{
at,
EventDetails,
EventFilter,
Events,
FilterEvents,
RawEventDetails,
};
/// Subscribe to events from blocks.
///
/// **Note:** these blocks haven't necessarily been finalised yet; prefer
/// [`Events::subscribe_finalized()`] if that is important.
///
/// **Note:** This function is hidden from the documentation
/// and is exposed only to be called via the codegen. It may
/// break between minor releases.
#[doc(hidden)]
pub async fn subscribe<T: Config, Evs: Decode + 'static>(
client: &Client<T>,
) -> Result<EventSubscription<EventSub<T::Header>, T, Evs>, BasicError> {
let block_subscription = client.rpc().subscribe_blocks().await?;
Ok(EventSubscription::new(client, block_subscription))
}
/// Subscribe to events from finalized blocks.
///
/// **Note:** This function is hidden from the documentation
/// and is exposed only to be called via the codegen. It may
/// break between minor releases.
#[doc(hidden)]
pub async fn subscribe_finalized<T: Config, Evs: Decode + 'static>(
client: &Client<T>,
) -> Result<EventSubscription<FinalizedEventSub<T::Header>, T, Evs>, BasicError> {
// fetch the last finalised block details immediately, so that we'll get
// events for each block after this one.
let last_finalized_block_hash = client.rpc().finalized_head().await?;
let last_finalized_block_number = client
.rpc()
.header(Some(last_finalized_block_hash))
.await?
.map(|h| (*h.number()).into());
// Fill in any gaps between the block above and the finalized blocks reported.
let block_subscription = subscribe_to_block_headers_filling_in_gaps(
client,
last_finalized_block_number,
client.rpc().subscribe_finalized_blocks().await?,
);
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
}
/// Take a subscription that returns block headers, and if any block numbers are missed out
/// betweem the block number provided and what's returned from the subscription, we fill in
/// the gaps and get hold of all intermediate block headers.
///
/// **Note:** This is exposed so that we can run integration tests on it, but otherwise
/// should not be used directly and may break between minor releases.
#[doc(hidden)]
pub fn subscribe_to_block_headers_filling_in_gaps<'a, S, E, T: Config>(
client: &'a Client<T>,
mut last_block_num: Option<u64>,
sub: S,
) -> impl Stream<Item = Result<T::Header, BasicError>> + Send + 'a
where
S: Stream<Item = Result<T::Header, E>> + Send + 'a,
E: Into<BasicError> + Send + 'static,
{
sub.flat_map(move |s| {
// Get the header, or return a stream containing just the error. Our EventSubscription
// stream will return `None` as soon as it hits an error like this.
let header = match s {
Ok(header) => header,
Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
};
// We want all previous details up to, but not including this current block num.
let end_block_num = (*header.number()).into();
// This is one after the last block we returned details for last time.
let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
// Iterate over all of the previous blocks we need headers for, ignoring the current block
// (which we already have the header info for):
let previous_headers = stream::iter(start_block_num..end_block_num)
.then(move |n| {
async move {
let hash = client.rpc().block_hash(Some(n.into())).await?;
let header = client.rpc().header(hash).await?;
Ok::<_, BasicError>(header)
}
})
.filter_map(|h| async { h.transpose() });
// On the next iteration, we'll get details starting just after this end block.
last_block_num = Some(end_block_num);
// Return a combination of any previous headers plus the new header.
Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
})
}
/// A `jsonrpsee` Subscription. This forms a part of the `EventSubscription` type handed back
/// in codegen from `subscribe_finalized`, and is exposed to be used in codegen.
#[doc(hidden)]
pub type FinalizedEventSub<'a, Header> = BoxStream<'a, Result<Header, BasicError>>;
pub type FinalizedEventSub<Header> = BoxStream<'static, Result<Header, Error>>;
/// A `jsonrpsee` Subscription. This forms a part of the `EventSubscription` type handed back
/// in codegen from `subscribe`, and is exposed to be used in codegen.
@@ -144,52 +44,80 @@ pub type EventSub<Item> = Subscription<Item>;
/// A subscription to events that implements [`Stream`], and returns [`Events`] objects for each block.
#[derive(Derivative)]
#[derivative(Debug(bound = "Sub: std::fmt::Debug"))]
pub struct EventSubscription<'a, Sub, T: Config, Evs: 'static> {
#[derivative(Debug(bound = "Sub: std::fmt::Debug, Client: std::fmt::Debug"))]
pub struct EventSubscription<T: Config, Client, Sub> {
finished: bool,
client: &'a Client<T>,
client: Client,
block_header_subscription: Sub,
#[derivative(Debug = "ignore")]
at: Option<
std::pin::Pin<
Box<dyn Future<Output = Result<Events<T, Evs>, BasicError>> + Send + 'a>,
>,
>,
_event_type: std::marker::PhantomData<Evs>,
at: Option<std::pin::Pin<Box<dyn Future<Output = Result<Events<T>, Error>> + Send>>>,
}
impl<'a, Sub, T: Config, Evs: Decode, E: Into<BasicError>>
EventSubscription<'a, Sub, T, Evs>
impl<T: Config, Client, Sub, E: Into<Error>> EventSubscription<T, Client, Sub>
where
Sub: Stream<Item = Result<T::Header, E>> + Unpin + 'a,
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
{
fn new(client: &'a Client<T>, block_header_subscription: Sub) -> Self {
/// Create a new [`EventSubscription`] from a client and a subscription
/// which returns block headers.
pub fn new(client: Client, block_header_subscription: Sub) -> Self {
EventSubscription {
finished: false,
client,
block_header_subscription,
at: None,
_event_type: std::marker::PhantomData,
}
}
/// Return only specific events matching the tuple of 1 or more event
/// types that has been provided as the `Filter` type parameter.
pub fn filter_events<Filter: EventFilter>(self) -> FilterEvents<'a, Self, T, Filter> {
///
/// # Example
///
/// ```no_run
/// use futures::StreamExt;
/// use subxt::{OnlineClient, PolkadotConfig};
///
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
/// pub mod polkadot {}
///
/// # #[tokio::main]
/// # async fn main() {
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// let mut events = api
/// .events()
/// .subscribe()
/// .await
/// .unwrap()
/// .filter_events::<(
/// polkadot::balances::events::Transfer,
/// polkadot::balances::events::Deposit
/// )>();
///
/// while let Some(ev) = events.next().await {
/// let event_details = ev.unwrap();
/// match event_details.event {
/// (Some(transfer), None) => println!("Balance transfer event: {transfer:?}"),
/// (None, Some(deposit)) => println!("Balance deposit event: {deposit:?}"),
/// _ => unreachable!()
/// }
/// }
/// # }
/// ```
pub fn filter_events<Filter: EventFilter>(
self,
) -> FilterEvents<'static, Self, T, Filter> {
FilterEvents::new(self)
}
}
impl<'a, T: Config, Sub: Unpin, Evs: Decode> Unpin
for EventSubscription<'a, Sub, T, Evs>
{
}
impl<T: Config, Client, Sub: Unpin> Unpin for EventSubscription<T, Client, Sub> {}
// We want `EventSubscription` to implement Stream. The below implementation is the rather verbose
// way to roughly implement the following function:
//
// ```
// fn subscribe_events<T: Config, Evs: Decode>(client: &'_ Client<T>, block_sub: Subscription<T::Header>) -> impl Stream<Item=Result<Events<'_, T, Evs>, BasicError>> + '_ {
// fn subscribe_events<T: Config, Evs: Decode>(client: &'_ Client<T>, block_sub: Subscription<T::Header>) -> impl Stream<Item=Result<Events<'_, T, Evs>, Error>> + '_ {
// use futures::StreamExt;
// block_sub.then(move |block_header_res| async move {
// use sp_runtime::traits::Header;
@@ -202,14 +130,14 @@ impl<'a, T: Config, Sub: Unpin, Evs: Decode> Unpin
//
// The advantage of this manual implementation is that we have a named type that we (and others)
// can derive things on, store away, alias etc.
impl<'a, Sub, T, Evs, E> Stream for EventSubscription<'a, Sub, T, Evs>
impl<T, Client, Sub, E> Stream for EventSubscription<T, Client, Sub>
where
T: Config,
Evs: Decode,
Sub: Stream<Item = Result<T::Header, E>> + Unpin + 'a,
E: Into<BasicError>,
Client: OnlineClientT<T>,
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
E: Into<Error>,
{
type Item = Result<Events<T, Evs>, BasicError>;
type Item = Result<Events<T>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
@@ -235,7 +163,9 @@ where
Some(Ok(block_header)) => {
// Note [jsdw]: We may be able to get rid of the per-item allocation
// with https://github.com/oblique/reusable-box-future.
self.at = Some(Box::pin(at(self.client, block_header.hash())));
let at = EventsClient::new(self.client.clone())
.at(Some(block_header.hash()));
self.at = Some(Box::pin(at));
// Continue, so that we poll this function future we've just created.
}
}
@@ -263,16 +193,16 @@ mod test {
fn assert_send<T: Send>() {}
assert_send::<
EventSubscription<
EventSub<<crate::DefaultConfig as Config>::Header>,
crate::DefaultConfig,
crate::SubstrateConfig,
(),
EventSub<<crate::SubstrateConfig as Config>::Header>,
>,
>();
assert_send::<
EventSubscription<
FinalizedEventSub<<crate::DefaultConfig as Config>::Header>,
crate::DefaultConfig,
crate::SubstrateConfig,
(),
FinalizedEventSub<<crate::SubstrateConfig as Config>::Header>,
>,
>();
}
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
client::OnlineClientT,
error::Error,
events::{
EventSub,
EventSubscription,
Events,
FinalizedEventSub,
},
Config,
};
use derivative::Derivative;
use futures::{
future::Either,
stream,
Stream,
StreamExt,
};
use sp_core::{
storage::StorageKey,
twox_128,
};
use sp_runtime::traits::Header;
use std::future::Future;
/// A client for working with events.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct EventsClient<T, Client> {
client: Client,
_marker: std::marker::PhantomData<T>,
}
impl<T, Client> EventsClient<T, Client> {
/// Create a new [`EventsClient`].
pub fn new(client: Client) -> Self {
Self {
client,
_marker: std::marker::PhantomData,
}
}
}
impl<T, Client> EventsClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Obtain events at some block hash.
pub fn at(
&self,
block_hash: Option<T::Hash>,
) -> impl Future<Output = Result<Events<T>, Error>> + 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 { at(client, block_hash).await }
}
/// Subscribe to all events from blocks.
///
/// **Note:** these blocks haven't necessarily been finalised yet; prefer
/// [`EventsClient::subscribe_finalized()`] if that is important.
///
/// # Example
///
/// ```no_run
/// # #[tokio::main]
/// # async fn main() {
/// use futures::StreamExt;
/// use subxt::{ OnlineClient, PolkadotConfig };
///
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// let mut events = api.events().subscribe().await.unwrap();
///
/// while let Some(ev) = events.next().await {
/// // Obtain all events from this block.
/// let ev = ev.unwrap();
/// // Print block hash.
/// println!("Event at block hash {:?}", ev.block_hash());
/// // Iterate over all events.
/// let mut iter = ev.iter();
/// while let Some(event_details) = iter.next() {
/// println!("Event details {:?}", event_details);
/// }
/// }
/// # }
/// ```
pub fn subscribe(
&self,
) -> impl Future<
Output = Result<EventSubscription<T, Client, EventSub<T::Header>>, Error>,
> + Send
+ 'static {
let client = self.client.clone();
async move { subscribe(client).await }
}
/// Subscribe to events from finalized blocks. See [`EventsClient::subscribe()`] for details.
pub fn subscribe_finalized(
&self,
) -> impl Future<
Output = Result<
EventSubscription<T, Client, FinalizedEventSub<T::Header>>,
Error,
>,
> + Send
+ 'static
where
Client: Send + Sync + 'static,
{
let client = self.client.clone();
async move { subscribe_finalized(client).await }
}
}
async fn at<T, Client>(
client: Client,
block_hash: Option<T::Hash>,
) -> Result<Events<T>, Error>
where
T: Config,
Client: OnlineClientT<T>,
{
// If block hash is not provided, get the hash
// for the latest block and use that.
let block_hash = match block_hash {
Some(hash) => hash,
None => {
client
.rpc()
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
}
};
let event_bytes = client
.rpc()
.storage(&*system_events_key().0, Some(block_hash))
.await?
.map(|e| e.0)
.unwrap_or_else(Vec::new);
Ok(Events::new(client.metadata(), block_hash, event_bytes))
}
async fn subscribe<T, Client>(
client: Client,
) -> Result<EventSubscription<T, Client, EventSub<T::Header>>, Error>
where
T: Config,
Client: OnlineClientT<T>,
{
let block_subscription = client.rpc().subscribe_blocks().await?;
Ok(EventSubscription::new(client, block_subscription))
}
/// Subscribe to events from finalized blocks.
async fn subscribe_finalized<T, Client>(
client: Client,
) -> Result<EventSubscription<T, Client, FinalizedEventSub<T::Header>>, Error>
where
T: Config,
Client: OnlineClientT<T>,
{
// fetch the last finalised block details immediately, so that we'll get
// events for each block after this one.
let last_finalized_block_hash = client.rpc().finalized_head().await?;
let last_finalized_block_number = client
.rpc()
.header(Some(last_finalized_block_hash))
.await?
.map(|h| (*h.number()).into());
let sub = client.rpc().subscribe_finalized_blocks().await?;
// Fill in any gaps between the block above and the finalized blocks reported.
let block_subscription = subscribe_to_block_headers_filling_in_gaps(
client.clone(),
last_finalized_block_number,
sub,
);
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
}
/// Note: This is exposed for testing but is not considered stable and may change
/// without notice in a patch release.
#[doc(hidden)]
pub fn subscribe_to_block_headers_filling_in_gaps<T, Client, S, E>(
client: Client,
mut last_block_num: Option<u64>,
sub: S,
) -> impl Stream<Item = Result<T::Header, Error>> + Send
where
T: Config,
Client: OnlineClientT<T> + Send + Sync,
S: Stream<Item = Result<T::Header, E>> + Send,
E: Into<Error> + Send + 'static,
{
sub.flat_map(move |s| {
let client = client.clone();
// Get the header, or return a stream containing just the error. Our EventSubscription
// stream will return `None` as soon as it hits an error like this.
let header = match s {
Ok(header) => header,
Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
};
// We want all previous details up to, but not including this current block num.
let end_block_num = (*header.number()).into();
// This is one after the last block we returned details for last time.
let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
// Iterate over all of the previous blocks we need headers for, ignoring the current block
// (which we already have the header info for):
let previous_headers = stream::iter(start_block_num..end_block_num)
.then(move |n| {
let client = client.clone();
async move {
let hash = client.rpc().block_hash(Some(n.into())).await?;
let header = client.rpc().header(hash).await?;
Ok::<_, Error>(header)
}
})
.filter_map(|h| async { h.transpose() });
// On the next iteration, we'll get details starting just after this end block.
last_block_num = Some(end_block_num);
// Return a combination of any previous headers plus the new header.
Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
})
}
// The storage key needed to access events.
fn system_events_key() -> StorageKey {
let mut storage_key = twox_128(b"System").to_vec();
storage_key.extend(twox_128(b"Events").to_vec());
StorageKey(storage_key)
}
+208 -435
View File
@@ -4,13 +4,15 @@
//! A representation of a block of events.
use crate::{
error::BasicError,
Client,
Config,
Event,
Metadata,
use super::{
Phase,
StaticEvent,
};
use crate::{
dynamic::DecodedValue,
error::Error,
Config,
Metadata,
};
use codec::{
Compact,
@@ -19,77 +21,48 @@ use codec::{
Input,
};
use derivative::Derivative;
use parking_lot::RwLock;
use sp_core::{
storage::StorageKey,
twox_128,
};
use std::sync::Arc;
/// Obtain events at some block hash. The generic parameter is what we
/// will attempt to decode each event into if using [`Events::iter()`],
/// and is expected to be the outermost event enum that contains all of
/// the possible events across all pallets.
///
/// **Note:** This function is hidden from the documentation
/// and is exposed only to be called via the codegen. Thus, prefer to use
/// `api.events().at(block_hash)` over calling this directly.
#[doc(hidden)]
pub async fn at<T: Config, Evs: Decode>(
client: &'_ Client<T>,
block_hash: T::Hash,
) -> Result<Events<T, Evs>, BasicError> {
let mut event_bytes = client
.rpc()
.storage(&system_events_key(), Some(block_hash))
.await?
.map(|s| s.0)
.unwrap_or_else(Vec::new);
// event_bytes is a SCALE encoded vector of events. So, pluck the
// compact encoded length from the front, leaving the remaining bytes
// for our iterating to decode.
//
// Note: if we get no bytes back, avoid an error reading vec length
// and default to 0 events.
let cursor = &mut &*event_bytes;
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
let event_bytes_len = event_bytes.len();
let remaining_len = cursor.len();
event_bytes.drain(0..event_bytes_len - remaining_len);
Ok(Events {
metadata: client.metadata(),
block_hash,
event_bytes,
num_events,
_event_type: std::marker::PhantomData,
})
}
// The storage key needed to access events.
fn system_events_key() -> StorageKey {
let mut storage_key = twox_128(b"System").to_vec();
storage_key.extend(twox_128(b"Events").to_vec());
StorageKey(storage_key)
}
/// A collection of events obtained from a block, bundled with the necessary
/// information needed to decode and iterate over them.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct Events<T: Config, Evs> {
metadata: Arc<RwLock<Metadata>>,
pub struct Events<T: Config> {
metadata: Metadata,
block_hash: T::Hash,
// Note; raw event bytes are prefixed with a Compact<u32> containing
// the number of events to be decoded. We should have stripped that off
// before storing the bytes here.
event_bytes: Vec<u8>,
event_bytes: Arc<[u8]>,
num_events: u32,
_event_type: std::marker::PhantomData<Evs>,
}
impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
impl<T: Config> Events<T> {
pub(crate) fn new(
metadata: Metadata,
block_hash: T::Hash,
mut event_bytes: Vec<u8>,
) -> Self {
// event_bytes is a SCALE encoded vector of events. So, pluck the
// compact encoded length from the front, leaving the remaining bytes
// for our iterating to decode.
//
// Note: if we get no bytes back, avoid an error reading vec length
// and default to 0 events.
let cursor = &mut &*event_bytes;
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
let event_bytes_len = event_bytes.len();
let remaining_len = cursor.len();
event_bytes.drain(0..event_bytes_len - remaining_len);
Self {
metadata,
block_hash,
event_bytes: event_bytes.into(),
num_events,
}
}
/// The number of events.
pub fn len(&self) -> u32 {
self.num_events
@@ -106,83 +79,23 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
self.block_hash
}
/// Iterate over the events, statically decoding them as we go.
/// If an event is encountered that cannot be statically decoded,
/// a [`codec::Error`] will be returned.
///
/// If the generated code does not know about all of the pallets that exist
/// in the runtime being targeted, it may not know about all of the
/// events either, and so this method should be avoided in favout of [`Events::iter_raw()`],
/// which uses runtime metadata to skip over unknown events.
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<Evs>, BasicError>> + '_ {
let event_bytes = &self.event_bytes;
) -> impl Iterator<Item = Result<EventDetails, Error>> + Send + Sync + 'static {
let event_bytes = self.event_bytes.clone();
let num_events = self.num_events;
let metadata = self.metadata.clone();
let mut pos = 0;
let mut index = 0;
std::iter::from_fn(move || {
let cursor = &mut &event_bytes[pos..];
let start_len = cursor.len();
if start_len == 0 || self.num_events == index {
None
} else {
let mut decode_one_event = || -> Result<_, BasicError> {
let phase = Phase::decode(cursor)?;
let ev = Evs::decode(cursor)?;
let _topics = Vec::<T::Hash>::decode(cursor)?;
Ok((phase, ev))
};
match decode_one_event() {
Ok((phase, event)) => {
// Skip over decoded bytes in next iteration:
pos += start_len - cursor.len();
// Gather the event details before incrementing the index for the next iter.
let res = Some(Ok(EventDetails {
phase,
index,
event,
}));
index += 1;
res
}
Err(e) => {
// By setting the position to the "end" of the event bytes,
// the cursor len will become 0 and the iterator will return `None`
// from now on:
pos = event_bytes.len();
Some(Err(e))
}
}
}
})
}
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
///
/// This method is safe to use even if you do not statically know about
/// all of the possible events; it splits events up using the metadata
/// obtained at runtime, which does.
pub fn iter_raw(
&self,
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + '_ {
let event_bytes = &self.event_bytes;
let metadata = {
let metadata = self.metadata.read();
metadata.clone()
};
let mut pos = 0;
let mut index = 0;
std::iter::from_fn(move || {
let cursor = &mut &event_bytes[pos..];
let start_len = cursor.len();
if start_len == 0 || self.num_events == index {
if start_len == 0 || num_events == index {
None
} else {
match decode_raw_event_details::<T>(&metadata, index, cursor) {
@@ -206,62 +119,11 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
})
}
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
///
/// This method is safe to use even if you do not statically know about
/// all of the possible events; it splits events up using the metadata
/// obtained at runtime, which does.
///
/// Unlike [`Events::iter_raw()`] this consumes `self`, which can be useful
/// if you need to store the iterator somewhere and avoid lifetime issues.
pub fn into_iter_raw(
self,
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + 'a {
let mut pos = 0;
let mut index = 0;
let metadata = {
let metadata = self.metadata.read();
metadata.clone()
};
std::iter::from_fn(move || {
let cursor = &mut &self.event_bytes[pos..];
let start_len = cursor.len();
if start_len == 0 || self.num_events == index {
None
} else {
match decode_raw_event_details::<T>(&metadata, index, cursor) {
Ok(raw_event) => {
// Skip over decoded bytes in next iteration:
pos += start_len - cursor.len();
// Increment the index:
index += 1;
// Return the event details:
Some(Ok(raw_event))
}
Err(e) => {
// By setting the position to the "end" of the event bytes,
// the cursor len will become 0 and the iterator will return `None`
// from now on:
pos = self.event_bytes.len();
Some(Err(e))
}
}
}
})
}
/// 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`.
///
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
/// use even if you do not statically know about all of the possible events.
pub fn find<Ev: Event>(&self) -> impl Iterator<Item = Result<Ev, BasicError>> + '_ {
self.iter_raw().filter_map(|ev| {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
@@ -269,67 +131,147 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
/// 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.
///
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
/// use even if you do not statically know about all of the possible events.
pub fn find_first<Ev: Event>(&self) -> Result<Option<Ev>, BasicError> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
/// Find an event that decodes to the type provided. Returns true if it was found.
///
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
/// use even if you do not statically know about all of the possible events.
pub fn has<Ev: crate::Event>(&self) -> Result<bool, BasicError> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
/// A decoded event and associated details.
/// The event details.
#[derive(Debug, Clone, PartialEq)]
pub struct EventDetails<Evs> {
/// During which [`Phase`] was the event produced?
pub phase: Phase,
/// What index is this event in the stored events for this block.
pub index: u32,
/// The event itself.
pub event: Evs,
pub struct EventDetails {
phase: Phase,
index: u32,
pallet: String,
variant: String,
bytes: Vec<u8>,
// Dev note: this is here because we've pretty much had to generate it
// anyway, but expect it to be generated on the fly in future versions,
// and so don't expose it.
fields: Vec<(Option<String>, DecodedValue)>,
}
/// A Value which has been decoded from some raw bytes.
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
/// The raw bytes for an event with associated details about
/// where and when it was emitted.
/// The raw data associated with some event.
#[derive(Debug, Clone, PartialEq)]
pub struct RawEventDetails {
pub struct EventDetailParts {
/// When was the event produced?
pub phase: Phase,
/// What index is this event in the stored events for this block.
pub index: u32,
/// The name of the pallet from whence the Event originated.
pub pallet: String,
/// The index of the pallet from whence the Event originated.
pub pallet_index: u8,
/// The name of the pallet Event variant.
/// The name of the pallet's Event variant.
pub variant: String,
/// The index of the pallet Event variant.
pub variant_index: u8,
/// The bytes representing the fields contained within the event.
/// All of the bytes representing this event, including the pallet
/// and variant index that the event originated from.
pub bytes: Vec<u8>,
/// Generic values representing each field of the event.
pub fields: Vec<DecodedValue>,
}
impl RawEventDetails {
/// Attempt to decode this [`RawEventDetails`] into a specific event.
pub fn as_event<E: Event>(&self) -> Result<Option<E>, CodecError> {
impl EventDetails {
/// Return the raw data associated with this event. Useful if you want
/// ownership over parts of the event data.
pub fn parts(self) -> EventDetailParts {
EventDetailParts {
phase: self.phase,
index: self.index,
pallet: self.pallet,
variant: self.variant,
bytes: self.bytes,
}
}
/// When was the event produced?
pub fn phase(&self) -> Phase {
self.phase
}
/// What index is this event in the stored events for this block.
pub fn index(&self) -> u32 {
self.index
}
/// The index of the pallet that the event originated from.
pub fn pallet_index(&self) -> u8 {
// Note: never panics because we set the first two bytes
// in `decode_event_details` to build this.
self.bytes[0]
}
/// The index of the event variant that the event originated from.
pub fn variant_index(&self) -> u8 {
// Note: never panics because we set the first two bytes
// in `decode_event_details` to build this.
self.bytes[1]
}
/// The name of the pallet from whence the Event originated.
pub fn pallet_name(&self) -> &str {
&self.pallet
}
/// The name of the pallet's Event variant.
pub fn variant_name(&self) -> &str {
&self.variant
}
/// Return the bytes representing this event, which include the pallet
/// and variant index that the event originated from.
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Return the bytes representing the fields stored in this event.
pub fn field_bytes(&self) -> &[u8] {
&self.bytes[2..]
}
/// Decode and provide the event fields back in the form of a composite
/// type, which represents either the named or unnamed fields that were
/// present.
// Dev note: if we can optimise Value decoding to avoid allocating
// while working through events, or if the event structure changes
// to allow us to skip over them, we'll no longer keep a copy of the
// decoded events in the event, and the actual decoding will happen
// when this method is called. This is why we return an owned vec and
// not a reference.
pub fn field_values(&self) -> scale_value::Composite<scale_value::scale::TypeId> {
if self.fields.is_empty() {
scale_value::Composite::Unnamed(vec![])
} else if self.fields[0].0.is_some() {
let named = self
.fields
.iter()
.map(|(n, f)| (n.clone().unwrap_or_default(), f.clone()))
.collect();
scale_value::Composite::Named(named)
} else {
let unnamed = self.fields.iter().map(|(_n, f)| f.clone()).collect();
scale_value::Composite::Unnamed(unnamed)
}
}
/// Attempt to decode these [`EventDetails`] into a specific static event.
/// This targets the fields within the event directly. You can also attempt to
/// decode the entirety of the event type (including the pallet and event
/// variants) using [`EventDetails::as_root_event()`].
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, CodecError> {
if self.pallet == E::PALLET && self.variant == E::EVENT {
Ok(Some(E::decode(&mut &self.bytes[..])?))
Ok(Some(E::decode(&mut &self.bytes[2..])?))
} else {
Ok(None)
}
}
/// 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: Decode>(&self) -> Result<E, CodecError> {
E::decode(&mut &self.bytes[..])
}
}
// Attempt to dynamically decode a single event from our events input.
@@ -337,7 +279,7 @@ fn decode_raw_event_details<T: Config>(
metadata: &Metadata,
index: u32,
input: &mut &[u8],
) -> Result<RawEventDetails, BasicError> {
) -> Result<EventDetails, Error> {
// Decode basic event details:
let phase = Phase::decode(input)?;
let pallet_index = input.read_byte()?;
@@ -358,19 +300,20 @@ fn decode_raw_event_details<T: Config>(
event_metadata.event()
);
// Use metadata to figure out which bytes belong to this event:
let mut event_bytes = Vec::new();
// Use metadata to figure out which bytes belong to this event.
// the event bytes also include the pallet/variant index so that, if we
// like, we can decode them quite easily into a top level event type.
let mut event_bytes = vec![pallet_index, variant_index];
let mut event_fields = Vec::new();
for arg in event_metadata.variant().fields() {
let type_id = arg.ty().id();
for (name, type_id) in event_metadata.fields() {
let all_bytes = *input;
// consume some bytes for each event field, moving the cursor forward:
let value = scale_value::scale::decode_as_type(
input,
type_id,
*type_id,
&metadata.runtime_metadata().types,
)?;
event_fields.push(value);
event_fields.push((name.clone(), value));
// count how many bytes were consumed based on remaining length:
let consumed_len = all_bytes.len() - input.len();
// move those consumed bytes to the output vec unaltered:
@@ -382,12 +325,10 @@ fn decode_raw_event_details<T: Config>(
let topics = Vec::<T::Hash>::decode(input)?;
tracing::debug!("topics: {:?}", topics);
Ok(RawEventDetails {
Ok(EventDetails {
phase,
index,
pallet_index,
pallet: event_metadata.pallet().to_string(),
variant_index,
variant: event_metadata.event().to_string(),
bytes: event_bytes,
fields: event_fields,
@@ -400,8 +341,7 @@ pub(crate) mod test_utils {
use super::*;
use crate::{
Config,
DefaultConfig,
Phase,
SubstrateConfig,
};
use codec::Encode;
use frame_metadata::{
@@ -431,7 +371,7 @@ pub(crate) mod test_utils {
pub struct EventRecord<E: Encode> {
phase: Phase,
event: AllEvents<E>,
topics: Vec<<DefaultConfig as Config>::Hash>,
topics: Vec<<SubstrateConfig as Config>::Hash>,
}
/// Build an EventRecord, which encoded events in the format expected
@@ -474,9 +414,9 @@ pub(crate) mod test_utils {
/// Build an `Events` object for test purposes, based on the details provided,
/// and with a default block hash.
pub fn events<E: Decode + Encode>(
metadata: Arc<RwLock<Metadata>>,
metadata: Metadata,
event_records: Vec<EventRecord<E>>,
) -> Events<DefaultConfig, AllEvents<E>> {
) -> Events<SubstrateConfig> {
let num_events = event_records.len() as u32;
let mut event_bytes = Vec::new();
for ev in event_records {
@@ -487,17 +427,16 @@ pub(crate) mod test_utils {
/// Much like [`events`], but takes pre-encoded events and event count, so that we can
/// mess with the bytes in tests if we need to.
pub fn events_raw<E: Decode + Encode>(
metadata: Arc<RwLock<Metadata>>,
pub fn events_raw(
metadata: Metadata,
event_bytes: Vec<u8>,
num_events: u32,
) -> Events<DefaultConfig, AllEvents<E>> {
) -> Events<SubstrateConfig> {
Events {
block_hash: <DefaultConfig as Config>::Hash::default(),
event_bytes,
block_hash: <SubstrateConfig as Config>::Hash::default(),
event_bytes: event_bytes.into(),
metadata,
num_events,
_event_type: std::marker::PhantomData,
}
}
}
@@ -509,18 +448,16 @@ mod tests {
event_record,
events,
events_raw,
AllEvents,
},
*,
};
use crate::Phase;
use codec::Encode;
use scale_info::TypeInfo;
use scale_value::Value;
/// Build a fake wrapped metadata.
fn metadata<E: TypeInfo + 'static>() -> Arc<RwLock<Metadata>> {
Arc::new(RwLock::new(test_utils::metadata::<E>()))
fn metadata<E: TypeInfo + 'static>() -> Metadata {
test_utils::metadata::<E>()
}
/// [`RawEventDetails`] can be annoying to test, because it contains
@@ -542,170 +479,42 @@ mod tests {
pub fn assert_raw_events_match(
// Just for convenience, pass in the metadata type constructed
// by the `metadata` function above to simplify caller code.
metadata: &Arc<RwLock<Metadata>>,
actual: RawEventDetails,
metadata: &Metadata,
actual: EventDetails,
expected: TestRawEventDetails,
) {
let metadata = metadata.read();
let types = &metadata.runtime_metadata().types;
// Make sure that the bytes handed back line up with the fields handed back;
// encode the fields back into bytes and they should be equal.
let mut actual_bytes = vec![];
for field in &actual.fields {
for (_name, field) in &actual.fields {
scale_value::scale::encode_as_type(
field.clone(),
field,
field.context,
types,
&mut actual_bytes,
)
.expect("should be able to encode properly");
}
assert_eq!(actual_bytes, actual.bytes);
assert_eq!(actual_bytes, actual.field_bytes());
let actual_fields_no_context: Vec<_> = actual
.fields
.into_iter()
.map(|f| f.remove_context())
.field_values()
.into_values()
.map(|value| value.remove_context())
.collect();
// Check each of the other fields:
assert_eq!(actual.phase, expected.phase);
assert_eq!(actual.index, expected.index);
assert_eq!(actual.pallet, expected.pallet);
assert_eq!(actual.pallet_index, expected.pallet_index);
assert_eq!(actual.variant, expected.variant);
assert_eq!(actual.variant_index, expected.variant_index);
assert_eq!(actual.phase(), expected.phase);
assert_eq!(actual.index(), expected.index);
assert_eq!(actual.pallet_name(), expected.pallet);
assert_eq!(actual.pallet_index(), expected.pallet_index);
assert_eq!(actual.variant_name(), expected.variant);
assert_eq!(actual.variant_index(), expected.variant_index);
assert_eq!(actual_fields_no_context, expected.fields);
}
#[test]
fn statically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construct an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![event_record(Phase::Finalization, Event::A(1))],
);
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(1))
}]
);
}
#[test]
fn statically_decode_multiple_events() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![
event_record(Phase::Initialization, Event::A(1)),
event_record(Phase::ApplyExtrinsic(123), Event::B(true)),
event_record(Phase::Finalization, Event::A(234)),
],
);
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![
EventDetails {
index: 0,
phase: Phase::Initialization,
event: AllEvents::Test(Event::A(1))
},
EventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
event: AllEvents::Test(Event::B(true))
},
EventDetails {
index: 2,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(234))
},
]
);
}
#[test]
fn statically_decode_multiple_events_until_error() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode 2 events:
let mut event_bytes = vec![];
event_record(Phase::Initialization, Event::A(1)).encode_to(&mut event_bytes);
event_record(Phase::ApplyExtrinsic(123), Event::B(true))
.encode_to(&mut event_bytes);
// Push a few naff bytes to the end (a broken third event):
event_bytes.extend_from_slice(&[3, 127, 45, 0, 2]);
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events_raw::<Event>(
metadata,
event_bytes,
3, // 2 "good" events, and then it'll hit the naff bytes.
);
let mut events_iter = events.iter();
assert_eq!(
events_iter.next().unwrap().unwrap(),
EventDetails {
index: 0,
phase: Phase::Initialization,
event: AllEvents::Test(Event::A(1))
}
);
assert_eq!(
events_iter.next().unwrap().unwrap(),
EventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
event: AllEvents::Test(Event::B(true))
}
);
// We'll hit an error trying to decode the third event:
assert!(events_iter.next().unwrap().is_err());
// ... and then "None" from then on.
assert!(events_iter.next().is_none());
assert!(events_iter.next().is_none());
}
#[test]
fn dynamically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
@@ -724,7 +533,7 @@ mod tests {
vec![event_record(Phase::ApplyExtrinsic(123), event)],
);
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
@@ -736,7 +545,7 @@ mod tests {
variant: "A".to_string(),
variant_index: 0,
fields: vec![
Value::uint(1u8),
Value::u128(1),
Value::bool(true),
Value::unnamed_composite(vec![Value::string("Hi")]),
],
@@ -771,7 +580,7 @@ mod tests {
],
);
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
@@ -783,7 +592,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(1u8)],
fields: vec![Value::u128(1)],
},
);
assert_raw_events_match(
@@ -809,7 +618,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(234u8)],
fields: vec![Value::u128(234)],
},
);
assert!(event_details.next().is_none());
@@ -837,13 +646,13 @@ mod tests {
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events_raw::<Event>(
let events = events_raw(
metadata.clone(),
event_bytes,
3, // 2 "good" events, and then it'll hit the naff bytes.
);
let mut events_iter = events.iter_raw();
let mut events_iter = events.iter();
assert_raw_events_match(
&metadata,
events_iter.next().unwrap().unwrap(),
@@ -854,7 +663,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(1u8)],
fields: vec![Value::u128(1)],
},
);
assert_raw_events_match(
@@ -895,20 +704,8 @@ mod tests {
vec![event_record(Phase::Finalization, Event::A(1))],
);
// Statically decode:
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(1))
}]
);
// Dynamically decode:
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
@@ -919,7 +716,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(1u8)],
fields: vec![Value::u128(1)],
},
);
assert!(event_details.next().is_none());
@@ -948,20 +745,8 @@ mod tests {
)],
);
// Statically decode:
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(CompactWrapper(1)))
}]
);
// Dynamically decode:
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
@@ -972,7 +757,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::unnamed_composite(vec![Value::uint(1u8)])],
fields: vec![Value::unnamed_composite(vec![Value::u128(1)])],
},
);
assert!(event_details.next().is_none());
@@ -1002,20 +787,8 @@ mod tests {
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
);
// Statically decode:
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(MyType::B))
}]
);
// Dynamically decode:
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
+55 -61
View File
@@ -4,14 +4,15 @@
//! Filtering individual events from subscriptions.
use super::Events;
use crate::{
BasicError,
Config,
Event,
use super::{
Events,
Phase,
StaticEvent,
};
use crate::{
Config,
Error,
};
use codec::Decode;
use futures::{
Stream,
StreamExt,
@@ -28,7 +29,7 @@ use std::{
/// exactly one of these will be `Some(event)` each iteration.
pub struct FilterEvents<'a, Sub: 'a, T: Config, Filter: EventFilter> {
// A subscription; in order for the Stream impl to apply, this will
// impl `Stream<Item = Result<Events<'a, T, Evs>, BasicError>> + Unpin + 'a`.
// impl `Stream<Item = Result<Events<'a, T, Evs>, Error>> + Unpin + 'a`.
sub: Sub,
// Each time we get Events from our subscription, they are stored here
// and iterated through in future stream iterations until exhausted.
@@ -37,7 +38,7 @@ pub struct FilterEvents<'a, Sub: 'a, T: Config, Filter: EventFilter> {
dyn Iterator<
Item = Result<
FilteredEventDetails<T::Hash, Filter::ReturnType>,
BasicError,
Error,
>,
> + Send
+ 'a,
@@ -56,14 +57,13 @@ impl<'a, Sub: 'a, T: Config, Filter: EventFilter> FilterEvents<'a, Sub, T, Filte
}
}
impl<'a, Sub, T, Evs, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
impl<'a, Sub, T, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
where
Sub: Stream<Item = Result<Events<T, Evs>, BasicError>> + Unpin + 'a,
Sub: Stream<Item = Result<Events<T>, Error>> + Unpin + 'a,
T: Config,
Evs: Decode + 'static,
Filter: EventFilter,
{
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, BasicError>;
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
@@ -112,14 +112,11 @@ pub trait EventFilter: private::Sealed {
/// The type we'll be handed back from filtering.
type ReturnType;
/// Filter the events based on the type implementing this trait.
fn filter<'a, T: Config, Evs: Decode + 'static>(
events: Events<T, Evs>,
fn filter<'a, T: Config>(
events: Events<T>,
) -> Box<
dyn Iterator<
Item = Result<
FilteredEventDetails<T::Hash, Self::ReturnType>,
BasicError,
>,
Item = Result<FilteredEventDetails<T::Hash, Self::ReturnType>, Error>,
> + Send
+ 'a,
>;
@@ -134,18 +131,16 @@ pub(crate) mod private {
// A special case impl for searching for a tuple of exactly one event (in this case, we don't
// need to return an `(Option<Event>,)`; we can just return `Event`.
impl<Ev: Event> private::Sealed for (Ev,) {}
impl<Ev: Event> EventFilter for (Ev,) {
impl<Ev: StaticEvent> private::Sealed for (Ev,) {}
impl<Ev: StaticEvent> EventFilter for (Ev,) {
type ReturnType = Ev;
fn filter<'a, T: Config, Evs: Decode + 'static>(
events: Events<T, Evs>,
fn filter<'a, T: Config>(
events: Events<T>,
) -> Box<
dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, BasicError>>
+ Send
+ 'a,
dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, Error>> + Send + 'a,
> {
let block_hash = events.block_hash();
let mut iter = events.into_iter_raw();
let mut iter = events.iter();
Box::new(std::iter::from_fn(move || {
for ev in iter.by_ref() {
// Forward any error immediately:
@@ -158,7 +153,7 @@ impl<Ev: Event> EventFilter for (Ev,) {
if let Ok(Some(event)) = ev {
// We found a match; return our tuple.
return Some(Ok(FilteredEventDetails {
phase: raw_event.phase,
phase: raw_event.phase(),
block_hash,
event,
}))
@@ -176,14 +171,14 @@ impl<Ev: Event> EventFilter for (Ev,) {
// A generalised impl for tuples of sizes greater than 1:
macro_rules! impl_event_filter {
($($ty:ident $idx:tt),+) => {
impl <$($ty: Event),+> private::Sealed for ( $($ty,)+ ) {}
impl <$($ty: Event),+> EventFilter for ( $($ty,)+ ) {
impl <$($ty: StaticEvent),+> private::Sealed for ( $($ty,)+ ) {}
impl <$($ty: StaticEvent),+> EventFilter for ( $($ty,)+ ) {
type ReturnType = ( $(Option<$ty>,)+ );
fn filter<'a, T: Config, Evs: Decode + 'static>(
events: Events<T, Evs>
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, BasicError>> + Send + 'a> {
fn filter<'a, T: Config>(
events: Events<T>
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, Error>> + Send + 'a> {
let block_hash = events.block_hash();
let mut iter = events.into_iter_raw();
let mut iter = events.iter();
Box::new(std::iter::from_fn(move || {
let mut out: ( $(Option<$ty>,)+ ) = Default::default();
for ev in iter.by_ref() {
@@ -199,7 +194,7 @@ macro_rules! impl_event_filter {
// We found a match; return our tuple.
out.$idx = Some(ev);
return Some(Ok(FilteredEventDetails {
phase: raw_event.phase,
phase: raw_event.phase(),
block_hash,
event: out
}))
@@ -232,24 +227,24 @@ mod test {
event_record,
events,
metadata,
AllEvents,
},
*,
};
use crate::{
Config,
DefaultConfig,
Metadata,
SubstrateConfig,
};
use codec::{
Decode,
Encode,
};
use codec::Encode;
use futures::{
stream,
Stream,
StreamExt,
};
use parking_lot::RwLock;
use scale_info::TypeInfo;
use std::sync::Arc;
// Some pretend events in a pallet
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
@@ -262,7 +257,7 @@ mod test {
// An event in our pallet that we can filter on.
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
struct EventA(u8);
impl crate::Event for EventA {
impl StaticEvent for EventA {
const PALLET: &'static str = "Test";
const EVENT: &'static str = "A";
}
@@ -270,7 +265,7 @@ mod test {
// An event in our pallet that we can filter on.
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
struct EventB(bool);
impl crate::Event for EventB {
impl StaticEvent for EventB {
const PALLET: &'static str = "Test";
const EVENT: &'static str = "B";
}
@@ -278,16 +273,15 @@ mod test {
// An event in our pallet that we can filter on.
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
struct EventC(u8, bool);
impl crate::Event for EventC {
impl StaticEvent for EventC {
const PALLET: &'static str = "Test";
const EVENT: &'static str = "C";
}
// A stream of fake events for us to try filtering on.
fn events_stream(
metadata: Arc<RwLock<Metadata>>,
) -> impl Stream<Item = Result<Events<DefaultConfig, AllEvents<PalletEvents>>, BasicError>>
{
metadata: Metadata,
) -> impl Stream<Item = Result<Events<SubstrateConfig>, Error>> {
stream::iter(vec![
events::<PalletEvents>(
metadata.clone(),
@@ -312,16 +306,16 @@ mod test {
],
),
])
.map(Ok::<_, BasicError>)
.map(Ok::<_, Error>)
}
#[tokio::test]
async fn filter_one_event_from_stream() {
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
let metadata = metadata::<PalletEvents>();
// Filter out fake event stream to select events matching `EventA` only.
let actual: Vec<_> =
FilterEvents::<_, DefaultConfig, (EventA,)>::new(events_stream(metadata))
FilterEvents::<_, SubstrateConfig, (EventA,)>::new(events_stream(metadata))
.map(|e| e.unwrap())
.collect()
.await;
@@ -329,17 +323,17 @@ mod test {
let expected = vec![
FilteredEventDetails {
phase: Phase::Initialization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: EventA(1),
},
FilteredEventDetails {
phase: Phase::Finalization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: EventA(2),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(3),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: EventA(3),
},
];
@@ -349,10 +343,10 @@ mod test {
#[tokio::test]
async fn filter_some_events_from_stream() {
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
let metadata = metadata::<PalletEvents>();
// Filter out fake event stream to select events matching `EventA` or `EventB`.
let actual: Vec<_> = FilterEvents::<_, DefaultConfig, (EventA, EventB)>::new(
let actual: Vec<_> = FilterEvents::<_, SubstrateConfig, (EventA, EventB)>::new(
events_stream(metadata),
)
.map(|e| e.unwrap())
@@ -362,32 +356,32 @@ mod test {
let expected = vec![
FilteredEventDetails {
phase: Phase::Initialization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (Some(EventA(1)), None),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(0),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (None, Some(EventB(true))),
},
FilteredEventDetails {
phase: Phase::Finalization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (Some(EventA(2)), None),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(1),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (None, Some(EventB(false))),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(2),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (None, Some(EventB(true))),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(3),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (Some(EventA(3)), None),
},
];
@@ -397,11 +391,11 @@ mod test {
#[tokio::test]
async fn filter_no_events_from_stream() {
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
let metadata = metadata::<PalletEvents>();
// Filter out fake event stream to select events matching `EventC` (none exist).
let actual: Vec<_> =
FilterEvents::<_, DefaultConfig, (EventC,)>::new(events_stream(metadata))
FilterEvents::<_, SubstrateConfig, (EventC,)>::new(events_stream(metadata))
.map(|e| e.unwrap())
.collect()
.await;
+43 -86
View File
@@ -2,108 +2,65 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module exposes the ability to work with events generated by a given block.
//! Subxt can either attempt to statically decode events into known types, or it
//! can hand back details of the raw event without knowing what shape its contents
//! are (this may be useful if we don't know what exactly we're looking for).
//!
//! This module is wrapped by the generated API in `RuntimeAPI::EventsApi`.
//!
//! # Examples
//!
//! ## Subscribe to all events
//!
//! Users can subscribe to all emitted events from blocks using `subscribe()`.
//!
//! To subscribe to all events from just the finalized blocks use `subscribe_finalized()`.
//!
//! To obtain the events from a given block use `at()`.
//!
//! ```no_run
//! # use futures::StreamExt;
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! # pub mod polkadot {}
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! let mut events = api.events().subscribe().await.unwrap();
//!
//! while let Some(ev) = events.next().await {
//! // Obtain all events from this block.
//! let ev: subxt::Events<_, _> = ev.unwrap();
//! // Print block hash.
//! println!("Event at block hash {:?}", ev.block_hash());
//! // Iterate over all events.
//! let mut iter = ev.iter();
//! while let Some(event_details) = iter.next() {
//! println!("Event details {:?}", event_details);
//! }
//! }
//! # }
//! ```
//!
//! ## Filter events
//!
//! The subxt exposes the ability to filter events via the `filter_events()` function.
//!
//! The function filters events from the provided tuple. If 1-tuple is provided, the events are
//! returned directly. Otherwise, we'll be given a corresponding tuple of `Option`'s, with exactly
//! one variant populated each time.
//!
//! ```no_run
//! # use futures::StreamExt;
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//!
//! let mut transfer_events = api
//! .events()
//! .subscribe()
//! .await
//! .unwrap()
//! .filter_events::<(polkadot::balances::events::Transfer,)>();
//!
//! while let Some(transfer_event) = transfer_events.next().await {
//! println!("Balance transfer event: {transfer_event:?}");
//! }
//! # }
//! ```
//! This module exposes the types and such necessary for working with events.
//! The two main entry points into events are [`crate::OnlineClient::events()`]
//! and calls like [crate::tx::TxProgress::wait_for_finalized_success()].
mod event_subscription;
mod events_client;
mod events_type;
mod filter_events;
pub use event_subscription::{
subscribe,
subscribe_finalized,
subscribe_to_block_headers_filling_in_gaps,
EventSub,
EventSubscription,
FinalizedEventSub,
};
pub use events_client::{
// Exposed only for testing:
subscribe_to_block_headers_filling_in_gaps,
EventsClient,
};
pub use events_type::{
at,
DecodedValue,
EventDetails,
Events,
RawEventDetails,
};
pub use filter_events::{
EventFilter,
FilterEvents,
FilteredEventDetails,
};
use codec::{
Decode,
Encode,
};
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
/// Generated API structures that represent an event implement this trait.
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait StaticEvent: Decode {
/// Pallet name.
const PALLET: &'static str;
/// Event name.
const EVENT: &'static str;
/// Returns true if the given pallet and event names match this event.
fn is_event(pallet: &str, event: &str) -> bool {
Self::PALLET == pallet && Self::EVENT == event
}
}
/// A phase of a block's execution.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Decode, Encode)]
pub enum Phase {
/// Applying an extrinsic.
ApplyExtrinsic(u32),
/// Finalizing the block.
Finalization,
/// Initializing the block.
Initialization,
}
+75 -338
View File
@@ -10,164 +10,80 @@
//! - [Query constants](https://docs.substrate.io/how-to-guides/v3/basics/configurable-constants/) (Constants)
//! - [Subscribe to events](https://docs.substrate.io/v3/runtime/events-and-errors/) (Events)
//!
//! # Initializing the API client
//!
//! # Generate the runtime API
//! To interact with a node, you'll need to construct a client.
//!
//! Subxt generates a runtime API from downloaded static metadata. The metadata can be downloaded using the
//! ```no_run
//! use subxt::{OnlineClient, PolkadotConfig};
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//! # }
//! ```
//!
//! This default client connects to a locally running node, but can be configured to point anywhere.
//! Additionally, an [`crate::OfflineClient`] is available to perform operations that don't require a
//! network connection to a node.
//!
//! The client takes a type parameter, here [`crate::PolkadotConfig`], which bakes in assumptions about
//! the structure of extrinsics and the underlying types used by the node for things like block numbers.
//! If the node you'd like to interact with deviates from Polkadot or the default Substrate node in these
//! areas, you'll need to configure them by implementing the [`crate::config::Config`] type yourself.
//!
//! # Generating runtime types
//!
//! Subxt can generate types at compile time to help you interact with a node. The metadata can be downloaded using the
//! [subxt-cli](https://crates.io/crates/subxt-cli) tool.
//!
//! To generate the runtime API, use the `subxt` attribute which points at downloaded static metadata.
//! To generate the types, use the `subxt` attribute which points at downloaded static metadata.
//!
//! ```ignore
//! #[subxt::subxt(runtime_metadata_path = "metadata.scale")]
//! pub mod node_runtime { }
//! ```
//!
//! The `node_runtime` has the following hierarchy:
//! For more information, please visit the [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/)
//! documentation.
//!
//! ```rust
//! pub mod node_runtime {
//! pub mod PalletName {
//! pub mod calls { }
//! pub mod storage { }
//! pub mod constants { }
//! pub mod events { }
//! }
//! }
//! ```
//! # Interacting with the API
//!
//! For more information regarding the `node_runtime` hierarchy, please visit the
//! [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/) documentation.
//! Once instantiated, a client, exposes four functions:
//! - `.tx()` for submitting extrinsics/transactions. See [`crate::tx::TxClient`] for more details, or see
//! the [balance_transfer](../examples/examples/balance_transfer.rs) example.
//! - `.storage()` for fetching and iterating over storage entries. See [`crate::storage::StorageClient`] for more details, or see
//! the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example.
//! - `.constants()` for getting hold of constants. See [`crate::constants::ConstantsClient`] for more details, or see
//! the [fetch_constants](../examples/examples/fetch_constants.rs) example.
//! - `.events()` for subscribing/obtaining events. See [`crate::events::EventsClient`] for more details, or see:
//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks.
//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event.
//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event.
//!
//! # Static Metadata Validation
//!
//! # Initializing the API client
//! If you use types generated by the [`crate::subxt`] macro, there is a chance that they will fall out of sync
//! with the actual state of the node you're trying to interact with.
//!
//! When you attempt to use any of these static types to interact with a node, Subxt will validate that they are
//! still compatible and issue an error if they have deviated.
//!
//! Additionally, you can validate that the entirety of the statically generated code aligns with a node like so:
//!
//! ```no_run
//! use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! use subxt::{OnlineClient, PolkadotConfig};
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = ClientBuilder::new()
//! .set_url("wss://rpc.polkadot.io:443")
//! .build()
//! .await
//! .unwrap()
//! .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! # }
//! ```
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! The `RuntimeApi` type is generated by the `subxt` macro from the supplied metadata. This can be parameterized with user
//! supplied implementations for the `Config` and `Extra` types, if the default implementation differs from the target
//! chain.
//!
//! To ensure that extrinsics are properly submitted, during the build phase of the Client the
//! runtime metadata of the node is downloaded. If the URL is not specified (`set_url`), the local host is used instead.
//!
//!
//! # Submit Extrinsics
//!
//! Extrinsics are obtained using the API's `RuntimeApi::tx()` method, followed by `pallet_name()` and then the
//! `call_item_name()`.
//!
//! Submit an extrinsic, returning success once the transaction is validated and accepted into the pool:
//!
//! Please visit the [balance_transfer](../examples/examples/balance_transfer.rs) example for more details.
//!
//!
//! # Querying Storage
//!
//! The runtime storage is queried via the generated `RuntimeApi::storage()` method, followed by the `pallet_name()` and
//! then the `storage_item_name()`.
//!
//! Please visit the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example for more details.
//!
//! # Query Constants
//!
//! Constants are embedded into the node's metadata.
//!
//! The subxt offers the ability to query constants from the runtime metadata (metadata downloaded when constructing
//! the client, *not* the one provided for API generation).
//!
//! To query constants use the generated `RuntimeApi::constants()` method, followed by the `pallet_name()` and then the
//! `constant_item_name()`.
//!
//! Please visit the [fetch_constants](../examples/examples/fetch_constants.rs) example for more details.
//!
//! # Subscribe to Events
//!
//! To subscribe to events, use the generated `RuntimeApi::events()` method which exposes:
//! - `subscribe()` - Subscribe to events emitted from blocks. These blocks haven't necessarily been finalised.
//! - `subscribe_finalized()` - Subscribe to events from finalized blocks.
//! - `at()` - Obtain events at a given block hash.
//!
//!
//! *Examples*
//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks.
//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event.
//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event.
//!
//! # Static Metadata Validation
//!
//! There are two types of metadata that the subxt is aware of:
//! - static metadata: Metadata used for generating the API.
//! - runtime metadata: Metadata downloaded from the target node when a subxt client is created.
//!
//! There are cases when the static metadata is different from the runtime metadata of a node.
//! Such is the case when the node performs a runtime update.
//!
//! To ensure that subxt can properly communicate with the target node the static metadata is validated
//! against the runtime metadata of the node.
//!
//! This validation is performed at the Call, Constant, and Storage levels, as well for the entire metadata.
//! The level of granularity ensures that the users can still submit a given call, even if another
//! call suffered changes.
//!
//! Full metadata validation:
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! # pub mod polkadot {}
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // To make sure that all of our statically generated pallets are compatible with the
//! // runtime node, we can run this check:
//! api.validate_metadata().unwrap();
//! # }
//! ```
//!
//! Call level validation:
//!
//! ```ignore
//! # use sp_keyring::AccountKeyring;
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! # pub mod polkadot {}
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // Submit the `transfer` extrinsic from Alice's account to Bob's.
//! let dest = AccountKeyring::Bob.to_account_id().into();
//!
//! let extrinsic = api
//! .tx()
//! .balances()
//! // Constructing an extrinsic will fail if the metadata
//! // is not in sync with the generated API.
//! .transfer(dest, 123_456_789_012_345)
//! .unwrap();
//! if let Err(_e) = polkadot::validate_codegen(&api) {
//! println!("Generated code is not up to date with node we're connected to");
//! }
//! # }
//! ```
//!
@@ -204,221 +120,42 @@
)]
#![allow(clippy::type_complexity)]
pub use frame_metadata::StorageHasher;
pub use subxt_macro::subxt;
pub use bitvec;
pub use codec;
pub use sp_core;
pub use sp_runtime;
use codec::{
Decode,
DecodeAll,
Encode,
};
use core::fmt::Debug;
use derivative::Derivative;
mod client;
mod config;
mod error;
pub mod client;
pub mod config;
pub mod constants;
pub mod dynamic;
pub mod error;
pub mod events;
pub mod extrinsic;
mod metadata;
pub mod metadata;
pub mod rpc;
pub mod storage;
mod transaction;
pub mod updates;
pub mod tx;
pub mod utils;
// Expose a few of the most common types at root,
// but leave most types behind their respoctive modules.
pub use crate::{
client::{
Client,
ClientBuilder,
SubmittableExtrinsic,
OfflineClient,
OnlineClient,
},
config::{
Config,
DefaultConfig,
},
error::{
BasicError,
Error,
GenericError,
HasModuleError,
ModuleError,
ModuleErrorData,
RuntimeError,
TransactionError,
},
events::{
EventDetails,
Events,
RawEventDetails,
},
extrinsic::{
PairSigner,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder,
SubstrateExtrinsicParams,
SubstrateExtrinsicParamsBuilder,
},
metadata::{
ErrorMetadata,
Metadata,
MetadataError,
PalletMetadata,
},
rpc::{
BlockNumber,
ReadProof,
RpcClient,
SystemProperties,
},
storage::{
KeyIter,
StorageEntry,
StorageEntryKey,
StorageMapKey,
},
transaction::{
TransactionEvents,
TransactionInBlock,
TransactionProgress,
TransactionStatus,
PolkadotConfig,
SubstrateConfig,
},
error::Error,
metadata::Metadata,
};
/// Trait to uniquely identify the call (extrinsic)'s identity from the runtime metadata.
///
/// Generated API structures that represent each of the different possible
/// calls to a node each implement this trait.
///
/// When encoding an extrinsic, we use this information to know how to map
/// the call to the specific pallet and call index needed by a particular node.
pub trait Call: Encode {
/// Pallet name.
const PALLET: &'static str;
/// Function name.
const FUNCTION: &'static str;
/// Returns true if the given pallet and function names match this call.
fn is_call(pallet: &str, function: &str) -> bool {
Self::PALLET == pallet && Self::FUNCTION == function
}
/// Re-export external crates that are made use of in the subxt API.
pub mod ext {
pub use bitvec;
pub use codec;
pub use frame_metadata;
pub use scale_value;
pub use sp_core;
pub use sp_runtime;
}
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
/// Generated API structures that represent an event implement this trait.
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait Event: Decode {
/// Pallet name.
const PALLET: &'static str;
/// Event name.
const EVENT: &'static str;
/// Returns true if the given pallet and event names match this event.
fn is_event(pallet: &str, event: &str) -> bool {
Self::PALLET == pallet && Self::EVENT == event
}
}
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Encoded(pub Vec<u8>);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec<u8> {
self.0.to_owned()
}
}
/// A phase of a block's execution.
#[derive(Clone, Debug, Eq, PartialEq, Decode, Encode)]
pub enum Phase {
/// Applying an extrinsic.
ApplyExtrinsic(u32),
/// Finalizing the block.
Finalization,
/// Initializing the block.
Initialization,
}
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
///
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
/// access the real type `T` [`Self::try_decode`] needs to be used.
#[derive(Derivative, Encode, Decode)]
#[derivative(
Debug(bound = ""),
Clone(bound = ""),
PartialEq(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T: Decode> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T> {
T::decode_all(&mut &self.data[..]).ok()
}
/// Returns the length of the encoded `T`.
pub fn encoded_len(&self) -> usize {
self.data.len()
}
/// Returns the encoded data.
pub fn encoded(&self) -> &[u8] {
&self.data
}
/// Create from the given encoded `data`.
pub fn from_encoded(data: Vec<u8>) -> Self {
Self {
data,
_phantom: PhantomDataSendSync::new(),
}
}
}
/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).
#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
#[scale_info(skip_type_params(T))]
#[doc(hidden)]
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
impl<T> PhantomDataSendSync<T> {
pub(crate) fn new() -> Self {
Self(core::marker::PhantomData)
}
}
unsafe impl<T> Send for PhantomDataSendSync<T> {}
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// This represents a key-value collection and is SCALE compatible
/// with collections like BTreeMap. This has the same type params
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
@@ -0,0 +1,79 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Metadata;
use crate::{
dynamic::DecodedValue,
error::Error,
};
use codec::Decode;
use frame_metadata::StorageEntryType;
/// This trait is implemented for types which can be decoded with the help of metadata.
pub trait DecodeWithMetadata {
/// The type that we'll get back from decoding.
type Target;
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error>;
/// Decode a storage item using metadata. By default, this uses the metadata to
/// work out the type ID to use, but for static items we can short circuit this
/// lookup.
fn decode_storage_with_metadata(
bytes: &mut &[u8],
pallet_name: &str,
storage_entry: &str,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
let ty = &metadata.pallet(pallet_name)?.storage(storage_entry)?.ty;
let id = match ty {
StorageEntryType::Plain(ty) => ty.id(),
StorageEntryType::Map { value, .. } => value.id(),
};
Self::decode_with_metadata(bytes, id, metadata)
}
}
// Things can be dynamically decoded to our Value type:
impl DecodeWithMetadata for DecodedValue {
type Target = Self;
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
let res = scale_value::scale::decode_as_type(bytes, type_id, metadata.types())?;
Ok(res)
}
}
/// Any type implementing [`Decode`] can also be decoded with the help of metadata.
pub struct DecodeStaticType<T>(std::marker::PhantomData<T>);
impl<T: Decode> DecodeWithMetadata for DecodeStaticType<T> {
type Target = T;
fn decode_with_metadata(
bytes: &mut &[u8],
_type_id: u32,
_metadata: &Metadata,
) -> Result<Self::Target, Error> {
T::decode(bytes).map_err(|e| e.into())
}
fn decode_storage_with_metadata(
bytes: &mut &[u8],
_pallet_name: &str,
_storage_entry: &str,
_metadata: &Metadata,
) -> Result<Self::Target, Error> {
T::decode(bytes).map_err(|e| e.into())
}
}
@@ -0,0 +1,67 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::Value,
error::Error,
metadata::Metadata,
};
use codec::Encode;
/// This trait is implemented for types which can be encoded with the help of metadata.
pub trait EncodeWithMetadata {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error>;
}
impl EncodeWithMetadata for Value<()> {
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
scale_value::scale::encode_as_type(self, type_id, metadata.types(), bytes)
.map_err(|e| e.into())
}
}
/// Any type implementing [`Encode`] can also be encoded with the help of metadata.
pub struct EncodeStaticType<T>(pub T);
impl<T: Encode> EncodeWithMetadata for EncodeStaticType<T> {
fn encode_with_metadata(
&self,
_type_id: u32,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
self.0.encode_to(bytes);
Ok(())
}
}
// We can transparently Encode anything wrapped in EncodeStaticType, too.
impl<E: Encode> Encode for EncodeStaticType<E> {
fn size_hint(&self) -> usize {
self.0.size_hint()
}
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
self.0.encode_to(dest)
}
fn encode(&self) -> Vec<u8> {
self.0.encode()
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.using_encoded(f)
}
fn encoded_size(&self) -> usize {
self.0.encoded_size()
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// Locate an item of a known type in the metadata.
/// We should already know that the item we're looking
/// for is a call or event for instance, and then with this,
/// we can dig up details for that item in the metadata.
pub trait MetadataLocation {
/// The pallet in which the item lives.
fn pallet(&self) -> &str;
/// The name of the item.
fn item(&self) -> &str;
}
+146 -133
View File
@@ -3,7 +3,6 @@
// see LICENSE for license details.
use super::hash_cache::HashCache;
use crate::Call;
use codec::Error as CodecError;
use frame_metadata::{
PalletConstantMetadata,
@@ -16,8 +15,8 @@ use frame_metadata::{
use parking_lot::RwLock;
use scale_info::{
form::PortableForm,
PortableRegistry,
Type,
Variant,
};
use std::{
collections::HashMap,
@@ -75,7 +74,11 @@ struct MetadataInner {
metadata: RuntimeMetadataV14,
pallets: HashMap<String, PalletMetadata>,
events: HashMap<(u8, u8), EventMetadata>,
// Errors are hashed by pallet index.
errors: HashMap<(u8, u8), ErrorMetadata>,
// Type of the DispatchError type, which is what comes back if
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
@@ -93,7 +96,7 @@ pub struct Metadata {
impl Metadata {
/// Returns a reference to [`PalletMetadata`].
pub fn pallet(&self, name: &'static str) -> Result<&PalletMetadata, MetadataError> {
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
self.inner
.pallets
.get(name)
@@ -128,6 +131,16 @@ impl Metadata {
Ok(error)
}
/// Return the DispatchError type ID if it exists.
pub fn dispatch_error_ty(&self) -> Option<u32> {
self.inner.dispatch_error_ty
}
/// Return the type registry embedded within the metadata.
pub fn types(&self) -> &PortableRegistry {
&self.inner.metadata.types
}
/// Resolve a type definition.
pub fn resolve_type(&self, id: u32) -> Option<&Type<PortableForm>> {
self.inner.metadata.types.resolve(id)
@@ -139,23 +152,25 @@ impl Metadata {
}
/// Obtain the unique hash for a specific storage entry.
pub fn storage_hash<S: crate::StorageEntry>(
pub fn storage_hash(
&self,
pallet: &str,
storage: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_storage_hashes
.get_or_insert(S::PALLET, S::STORAGE, || {
subxt_metadata::get_storage_hash(
&self.inner.metadata,
S::PALLET,
S::STORAGE,
)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
}
})
.get_or_insert(pallet, storage, || {
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => {
MetadataError::PalletNotFound
}
subxt_metadata::NotFound::Item => {
MetadataError::StorageNotFound
}
}
})
})
}
@@ -183,21 +198,23 @@ impl Metadata {
}
/// Obtain the unique hash for a call.
pub fn call_hash<C: crate::Call>(&self) -> Result<[u8; 32], MetadataError> {
pub fn call_hash(
&self,
pallet: &str,
function: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_call_hashes
.get_or_insert(C::PALLET, C::FUNCTION, || {
subxt_metadata::get_call_hash(
&self.inner.metadata,
C::PALLET,
C::FUNCTION,
)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
.get_or_insert(pallet, function, || {
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => {
MetadataError::PalletNotFound
}
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
})
}
@@ -222,7 +239,8 @@ impl Metadata {
pub struct PalletMetadata {
index: u8,
name: String,
calls: HashMap<String, u8>,
call_indexes: HashMap<String, u8>,
call_ty_id: Option<u32>,
storage: HashMap<String, StorageEntryMetadata<PortableForm>>,
constants: HashMap<String, PalletConstantMetadata<PortableForm>>,
}
@@ -238,15 +256,18 @@ impl PalletMetadata {
self.index
}
/// If calls exist for this pallet, this returns the type ID of the variant
/// representing the different possible calls.
pub fn call_ty_id(&self) -> Option<u32> {
self.call_ty_id
}
/// Attempt to resolve a call into an index in this pallet, failing
/// if the call is not found in this pallet.
pub fn call_index<C>(&self) -> Result<u8, MetadataError>
where
C: Call,
{
pub fn call_index(&self, function: &str) -> Result<u8, MetadataError> {
let fn_index = *self
.calls
.get(C::FUNCTION)
.call_indexes
.get(function)
.ok_or(MetadataError::CallNotFound)?;
Ok(fn_index)
}
@@ -273,9 +294,12 @@ impl PalletMetadata {
/// Metadata for specific events.
#[derive(Clone, Debug)]
pub struct EventMetadata {
pallet: String,
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
event: String,
variant: Variant<PortableForm>,
fields: Vec<(Option<String>, u32)>,
docs: Vec<String>,
}
impl EventMetadata {
@@ -289,21 +313,25 @@ impl EventMetadata {
&self.event
}
/// Get the type def variant for the pallet event.
pub fn variant(&self) -> &Variant<PortableForm> {
&self.variant
/// The names and types of each field in the event.
pub fn fields(&self) -> &[(Option<String>, u32)] {
&self.fields
}
/// Documentation for this event.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Metadata for specific errors obtained from the pallet's `PalletErrorMetadata`.
///
/// This holds in memory information regarding the Pallet's name, Error's name, and the underlying
/// metadata representation.
/// Details about a specific runtime error.
#[derive(Clone, Debug)]
pub struct ErrorMetadata {
pallet: String,
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
error: String,
variant: Variant<PortableForm>,
docs: Vec<String>,
}
impl ErrorMetadata {
@@ -312,29 +340,31 @@ impl ErrorMetadata {
&self.pallet
}
/// Get the name of the specific pallet error.
/// The name of the error.
pub fn error(&self) -> &str {
&self.error
}
/// Get the description of the specific pallet error.
pub fn description(&self) -> &[String] {
self.variant.docs()
/// Documentation for the error.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Error originated from converting a runtime metadata [RuntimeMetadataPrefixed] to
/// the internal [Metadata] representation.
///
/// The runtime metadata is converted when building the [crate::client::Client].
#[derive(Debug, thiserror::Error)]
pub enum InvalidMetadataError {
/// Invalid prefix
#[error("Invalid prefix")]
InvalidPrefix,
/// Invalid version
#[error("Invalid version")]
InvalidVersion,
/// Type missing from type registry
#[error("Type {0} missing from type registry")]
MissingType(u32),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type")]
TypeDefNotVariant(u32),
}
@@ -366,15 +396,18 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
.pallets
.iter()
.map(|pallet| {
let calls = pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| {
let type_def_variant = get_type_def_variant(call.ty.id())?;
let calls = type_def_variant
.variants()
.iter()
.map(|v| (v.name().clone(), v.index()))
.collect();
Ok(calls)
})?;
let call_ty_id = pallet.calls.as_ref().map(|c| c.ty.id());
let call_indexes =
pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| {
let type_def_variant = get_type_def_variant(call.ty.id())?;
let call_indexes = type_def_variant
.variants()
.iter()
.map(|v| (v.name().clone(), v.index()))
.collect();
Ok(call_indexes)
})?;
let storage = pallet.storage.as_ref().map_or(HashMap::new(), |storage| {
storage
@@ -393,7 +426,8 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
let pallet_metadata = PalletMetadata {
index: pallet.index,
name: pallet.name.to_string(),
calls,
call_indexes,
call_ty_id,
storage,
constants,
};
@@ -402,55 +436,54 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
})
.collect::<Result<_, _>>()?;
let pallet_events = metadata
.pallets
.iter()
.filter_map(|pallet| {
pallet.event.as_ref().map(|event| {
let type_def_variant = get_type_def_variant(event.ty.id())?;
Ok((pallet, type_def_variant))
})
})
.collect::<Result<Vec<_>, _>>()?;
let events = pallet_events
.iter()
.flat_map(|(pallet, type_def_variant)| {
type_def_variant.variants().iter().map(move |var| {
let key = (pallet.index, var.index());
let value = EventMetadata {
pallet: pallet.name.clone(),
event: var.name().clone(),
variant: var.clone(),
};
(key, value)
})
})
.collect();
let mut events = HashMap::<(u8, u8), EventMetadata>::new();
for pallet in &metadata.pallets {
if let Some(event) = &pallet.event {
let pallet_name: Arc<str> = pallet.name.to_string().into();
let event_type_id = event.ty.id();
let event_variant = get_type_def_variant(event_type_id)?;
for variant in event_variant.variants() {
events.insert(
(pallet.index, variant.index()),
EventMetadata {
pallet: pallet_name.clone(),
event: variant.name().to_owned(),
fields: variant
.fields()
.iter()
.map(|f| (f.name().map(|n| n.to_owned()), f.ty().id()))
.collect(),
docs: variant.docs().to_vec(),
},
);
}
}
}
let pallet_errors = metadata
.pallets
let mut errors = HashMap::<(u8, u8), ErrorMetadata>::new();
for pallet in &metadata.pallets {
if let Some(error) = &pallet.error {
let pallet_name: Arc<str> = pallet.name.to_string().into();
let error_variant = get_type_def_variant(error.ty.id())?;
for variant in error_variant.variants() {
errors.insert(
(pallet.index, variant.index()),
ErrorMetadata {
pallet: pallet_name.clone(),
error: variant.name().clone(),
docs: variant.docs().to_vec(),
},
);
}
}
}
let dispatch_error_ty = metadata
.types
.types()
.iter()
.filter_map(|pallet| {
pallet.error.as_ref().map(|error| {
let type_def_variant = get_type_def_variant(error.ty.id())?;
Ok((pallet, type_def_variant))
})
})
.collect::<Result<Vec<_>, _>>()?;
let errors = pallet_errors
.iter()
.flat_map(|(pallet, type_def_variant)| {
type_def_variant.variants().iter().map(move |var| {
let key = (pallet.index, var.index());
let value = ErrorMetadata {
pallet: pallet.name.clone(),
error: var.name().clone(),
variant: var.clone(),
};
(key, value)
})
})
.collect();
.find(|ty| ty.ty().path().segments() == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id());
Ok(Metadata {
inner: Arc::new(MetadataInner {
@@ -458,6 +491,7 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
pallets,
events,
errors,
dispatch_error_ty,
cached_metadata_hash: Default::default(),
cached_call_hashes: Default::default(),
cached_constant_hashes: Default::default(),
@@ -470,7 +504,6 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
#[cfg(test)]
mod tests {
use super::*;
use crate::StorageEntryKey;
use frame_metadata::{
ExtrinsicMetadata,
PalletStorageMetadata,
@@ -553,14 +586,7 @@ mod tests {
fn metadata_call_inner_cache() {
let metadata = load_metadata();
#[derive(codec::Encode)]
struct ValidCall;
impl crate::Call for ValidCall {
const PALLET: &'static str = "System";
const FUNCTION: &'static str = "fill_block";
}
let hash = metadata.call_hash::<ValidCall>();
let hash = metadata.call_hash("System", "fill_block");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_call_hashes.get_or_insert(
@@ -601,20 +627,7 @@ mod tests {
#[test]
fn metadata_storage_inner_cache() {
let metadata = load_metadata();
#[derive(codec::Encode)]
struct ValidStorage;
impl crate::StorageEntry for ValidStorage {
const PALLET: &'static str = "System";
const STORAGE: &'static str = "Account";
type Value = ();
fn key(&self) -> StorageEntryKey {
unreachable!("Should not be called");
}
}
let hash = metadata.storage_hash::<ValidStorage>();
let hash = metadata.storage_hash("System", "Account");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_storage_hashes.get_or_insert(
+17
View File
@@ -2,9 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types representing the metadata obtained from a node.
mod decode_with_metadata;
mod encode_with_metadata;
mod hash_cache;
mod metadata_location;
mod metadata_type;
pub use metadata_location::MetadataLocation;
pub use metadata_type::{
ErrorMetadata,
EventMetadata,
@@ -13,3 +20,13 @@ pub use metadata_type::{
MetadataError,
PalletMetadata,
};
pub use decode_with_metadata::{
DecodeStaticType,
DecodeWithMetadata,
};
pub use encode_with_metadata::{
EncodeStaticType,
EncodeWithMetadata,
};
+61 -95
View File
@@ -7,67 +7,29 @@
//! This is used behind the scenes by various `subxt` APIs, but can
//! also be used directly.
//!
//! # Examples
//! # Example
//!
//! ## Fetch Storage
//! Fetching storage keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageKeyPrefix;
//! # use subxt::rpc::Rpc;
//! use subxt::{ PolkadotConfig, OnlineClient, storage::StorageKey };
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // Storage prefix is `twox_128("System") ++ twox_128("ExtrinsicCount")`.
//! let key = StorageKeyPrefix::new::<polkadot::system::storage::ExtrinsicCount>()
//! .to_storage_key();
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! // Obtain the RPC from a generated API
//! let rpc: &Rpc<_> = api
//! .client
//! .rpc();
//!
//! let result = rpc.storage(&key, None).await.unwrap();
//! println!("Storage result: {:?}", result);
//! # }
//! ```
//!
//! ## Fetch Keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageKeyPrefix;
//! # use subxt::rpc::Rpc;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! let key = StorageKeyPrefix::new::<polkadot::xcm_pallet::storage::VersionNotifiers>()
//! .to_storage_key();
//!
//! // Obtain the RPC from a generated API
//! let rpc: &Rpc<_> = api
//! .client
//! .rpc();
//! let key = polkadot::storage()
//! .xcm_pallet()
//! .version_notifiers_root()
//! .to_bytes();
//!
//! // Fetch up to 10 keys.
//! let keys = rpc
//! .storage_keys_paged(Some(key), 10, None, None)
//! let keys = api
//! .rpc()
//! .storage_keys_paged(&key, 10, None, None)
//! .await
//! .unwrap();
//!
@@ -88,10 +50,10 @@ use std::{
};
use crate::{
error::BasicError,
error::Error,
utils::PhantomDataSendSync,
Config,
Metadata,
PhantomDataSendSync,
};
use codec::{
Decode,
@@ -198,7 +160,7 @@ pub type SystemProperties = serde_json::Map<String, serde_json::Value>;
/// must be kept compatible with that type from the target substrate version.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SubstrateTransactionStatus<Hash, BlockHash> {
pub enum SubstrateTxStatus<Hash, BlockHash> {
/// Transaction is part of the future queue.
Future,
/// Transaction is part of the ready queue.
@@ -324,13 +286,13 @@ impl<T: Config> Rpc<T> {
}
}
/// Fetch a storage key
/// Fetch the raw bytes for a given storage key
pub async fn storage(
&self,
key: &StorageKey,
key: &[u8],
hash: Option<T::Hash>,
) -> Result<Option<StorageData>, BasicError> {
let params = rpc_params![key, hash];
) -> Result<Option<StorageData>, Error> {
let params = rpc_params![to_hex(key), hash];
let data = self.client.request("state_getStorage", params).await?;
Ok(data)
}
@@ -340,12 +302,13 @@ impl<T: Config> Rpc<T> {
/// If `start_key` is passed, return next keys in storage in lexicographic order.
pub async fn storage_keys_paged(
&self,
key: Option<StorageKey>,
key: &[u8],
count: u32,
start_key: Option<StorageKey>,
start_key: Option<&[u8]>,
hash: Option<T::Hash>,
) -> Result<Vec<StorageKey>, BasicError> {
let params = rpc_params![key, count, start_key, hash];
) -> Result<Vec<StorageKey>, Error> {
let start_key = start_key.map(to_hex);
let params = rpc_params![to_hex(key), count, start_key, hash];
let data = self.client.request("state_getKeysPaged", params).await?;
Ok(data)
}
@@ -353,10 +316,11 @@ impl<T: Config> Rpc<T> {
/// Query historical storage entries
pub async fn query_storage(
&self,
keys: Vec<StorageKey>,
keys: impl IntoIterator<Item = &[u8]>,
from: T::Hash,
to: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
) -> Result<Vec<StorageChangeSet<T::Hash>>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, from, to];
self.client
.request("state_queryStorage", params)
@@ -367,9 +331,10 @@ impl<T: Config> Rpc<T> {
/// Query historical storage entries
pub async fn query_storage_at(
&self,
keys: &[StorageKey],
keys: impl IntoIterator<Item = &[u8]>,
at: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
) -> Result<Vec<StorageChangeSet<T::Hash>>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, at];
self.client
.request("state_queryStorageAt", params)
@@ -378,7 +343,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch the genesis hash
pub async fn genesis_hash(&self) -> Result<T::Hash, BasicError> {
pub async fn genesis_hash(&self) -> Result<T::Hash, Error> {
let block_zero = 0u32;
let params = rpc_params![block_zero];
let genesis_hash: Option<T::Hash> =
@@ -387,7 +352,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch the metadata
pub async fn metadata(&self) -> Result<Metadata, BasicError> {
pub async fn metadata(&self) -> Result<Metadata, Error> {
let bytes: Bytes = self
.client
.request("state_getMetadata", rpc_params![])
@@ -398,7 +363,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch system properties
pub async fn system_properties(&self) -> Result<SystemProperties, BasicError> {
pub async fn system_properties(&self) -> Result<SystemProperties, Error> {
Ok(self
.client
.request("system_properties", rpc_params![])
@@ -406,22 +371,22 @@ impl<T: Config> Rpc<T> {
}
/// Fetch system health
pub async fn system_health(&self) -> Result<Health, BasicError> {
pub async fn system_health(&self) -> Result<Health, Error> {
Ok(self.client.request("system_health", rpc_params![]).await?)
}
/// Fetch system chain
pub async fn system_chain(&self) -> Result<String, BasicError> {
pub async fn system_chain(&self) -> Result<String, Error> {
Ok(self.client.request("system_chain", rpc_params![]).await?)
}
/// Fetch system name
pub async fn system_name(&self) -> Result<String, BasicError> {
pub async fn system_name(&self) -> Result<String, Error> {
Ok(self.client.request("system_name", rpc_params![]).await?)
}
/// Fetch system version
pub async fn system_version(&self) -> Result<String, BasicError> {
pub async fn system_version(&self) -> Result<String, Error> {
Ok(self.client.request("system_version", rpc_params![]).await?)
}
@@ -429,7 +394,7 @@ impl<T: Config> Rpc<T> {
pub async fn system_account_next_index(
&self,
account: &T::AccountId,
) -> Result<T::Index, BasicError> {
) -> Result<T::Index, Error> {
Ok(self
.client
.request("system_accountNextIndex", rpc_params![account])
@@ -440,7 +405,7 @@ impl<T: Config> Rpc<T> {
pub async fn header(
&self,
hash: Option<T::Hash>,
) -> Result<Option<T::Header>, BasicError> {
) -> Result<Option<T::Header>, Error> {
let params = rpc_params![hash];
let header = self.client.request("chain_getHeader", params).await?;
Ok(header)
@@ -450,14 +415,14 @@ impl<T: Config> Rpc<T> {
pub async fn block_hash(
&self,
block_number: Option<BlockNumber>,
) -> Result<Option<T::Hash>, BasicError> {
) -> Result<Option<T::Hash>, Error> {
let params = rpc_params![block_number];
let block_hash = self.client.request("chain_getBlockHash", params).await?;
Ok(block_hash)
}
/// Get a block hash of the latest finalized block
pub async fn finalized_head(&self) -> Result<T::Hash, BasicError> {
pub async fn finalized_head(&self) -> Result<T::Hash, Error> {
let hash = self
.client
.request("chain_getFinalizedHead", rpc_params![])
@@ -469,7 +434,7 @@ impl<T: Config> Rpc<T> {
pub async fn block(
&self,
hash: Option<T::Hash>,
) -> Result<Option<ChainBlock<T>>, BasicError> {
) -> Result<Option<ChainBlock<T>>, Error> {
let params = rpc_params![hash];
let block = self.client.request("chain_getBlock", params).await?;
Ok(block)
@@ -483,7 +448,7 @@ impl<T: Config> Rpc<T> {
pub async fn block_stats(
&self,
block_hash: T::Hash,
) -> Result<Option<BlockStats>, BasicError> {
) -> Result<Option<BlockStats>, Error> {
let params = rpc_params![block_hash];
let stats = self.client.request("dev_getBlockStats", params).await?;
Ok(stats)
@@ -492,9 +457,10 @@ impl<T: Config> Rpc<T> {
/// Get proof of storage entries at a specific block's state.
pub async fn read_proof(
&self,
keys: Vec<StorageKey>,
keys: impl IntoIterator<Item = &[u8]>,
hash: Option<T::Hash>,
) -> Result<ReadProof<T::Hash>, BasicError> {
) -> Result<ReadProof<T::Hash>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, hash];
let proof = self.client.request("state_getReadProof", params).await?;
Ok(proof)
@@ -504,7 +470,7 @@ impl<T: Config> Rpc<T> {
pub async fn runtime_version(
&self,
at: Option<T::Hash>,
) -> Result<RuntimeVersion, BasicError> {
) -> Result<RuntimeVersion, Error> {
let params = rpc_params![at];
let version = self
.client
@@ -514,7 +480,7 @@ impl<T: Config> Rpc<T> {
}
/// Subscribe to blocks.
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, BasicError> {
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
@@ -530,7 +496,7 @@ impl<T: Config> Rpc<T> {
/// Subscribe to finalized blocks.
pub async fn subscribe_finalized_blocks(
&self,
) -> Result<Subscription<T::Header>, BasicError> {
) -> Result<Subscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
@@ -545,7 +511,7 @@ impl<T: Config> Rpc<T> {
/// Subscribe to runtime version updates that produce changes in the metadata.
pub async fn subscribe_runtime_version(
&self,
) -> Result<Subscription<RuntimeVersion>, BasicError> {
) -> Result<Subscription<RuntimeVersion>, Error> {
let subscription = self
.client
.subscribe(
@@ -561,7 +527,7 @@ impl<T: Config> Rpc<T> {
pub async fn submit_extrinsic<X: Encode>(
&self,
extrinsic: X,
) -> Result<T::Hash, BasicError> {
) -> Result<T::Hash, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = rpc_params![bytes];
let xt_hash = self
@@ -575,8 +541,7 @@ impl<T: Config> Rpc<T> {
pub async fn watch_extrinsic<X: Encode>(
&self,
extrinsic: X,
) -> Result<Subscription<SubstrateTransactionStatus<T::Hash, T::Hash>>, BasicError>
{
) -> Result<Subscription<SubstrateTxStatus<T::Hash, T::Hash>>, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = rpc_params![bytes];
let subscription = self
@@ -596,14 +561,14 @@ impl<T: Config> Rpc<T> {
key_type: String,
suri: String,
public: Bytes,
) -> Result<(), BasicError> {
) -> Result<(), Error> {
let params = rpc_params![key_type, suri, public];
self.client.request("author_insertKey", params).await?;
Ok(())
}
/// Generate new session keys and returns the corresponding public keys.
pub async fn rotate_keys(&self) -> Result<Bytes, BasicError> {
pub async fn rotate_keys(&self) -> Result<Bytes, Error> {
Ok(self
.client
.request("author_rotateKeys", rpc_params![])
@@ -615,10 +580,7 @@ impl<T: Config> Rpc<T> {
/// `session_keys` is the SCALE encoded session keys object from the runtime.
///
/// Returns `true` iff all private keys could be found.
pub async fn has_session_keys(
&self,
session_keys: Bytes,
) -> Result<bool, BasicError> {
pub async fn has_session_keys(&self, session_keys: Bytes) -> Result<bool, Error> {
let params = rpc_params![session_keys];
Ok(self.client.request("author_hasSessionKeys", params).await?)
}
@@ -630,7 +592,7 @@ impl<T: Config> Rpc<T> {
&self,
public_key: Bytes,
key_type: String,
) -> Result<bool, BasicError> {
) -> Result<bool, Error> {
let params = rpc_params![public_key, key_type];
Ok(self.client.request("author_hasKey", params).await?)
}
@@ -642,8 +604,8 @@ impl<T: Config> Rpc<T> {
&self,
encoded_signed: &[u8],
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, BasicError> {
let params = rpc_params![format!("0x{}", hex::encode(encoded_signed)), at];
) -> Result<ApplyExtrinsicResult, Error> {
let params = rpc_params![to_hex(encoded_signed), at];
let result_bytes: Bytes = self.client.request("system_dryRun", params).await?;
let data: ApplyExtrinsicResult =
codec::Decode::decode(&mut result_bytes.0.as_slice())?;
@@ -669,6 +631,10 @@ async fn ws_transport(url: &str) -> Result<(WsSender, WsReceiver), RpcError> {
.map_err(|e| RpcError::Transport(e.into()))
}
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
#[cfg(test)]
mod test {
use super::*;
-376
View File
@@ -1,376 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Query the runtime storage using [StorageClient].
//!
//! This module is the core of performing runtime storage queries. While you can
//! work with it directly, it's prefer to use the generated `storage()` interface where
//! possible.
//!
//! The exposed API is performing RPC calls to `state_getStorage` and `state_getKeysPaged`.
//!
//! A runtime storage entry can be of type:
//! - [StorageEntryKey::Plain] for keys constructed just from the prefix
//! `twox_128(pallet) ++ twox_128(storage_item)`
//! - [StorageEntryKey::Map] for mapped keys constructed from the prefix,
//! plus other arguments `twox_128(pallet) ++ twox_128(storage_item) ++ hash(arg1) ++ arg1`
//!
//! # Examples
//!
//! ## Fetch Storage Keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageClient;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! # // Obtain the storage client wrapper from the API.
//! # let storage: StorageClient<_> = api.client.storage();
//! // Fetch just the keys, returning up to 10 keys.
//! let keys = storage
//! .fetch_keys::<polkadot::xcm_pallet::storage::VersionNotifiers>(10, None, None)
//! .await
//! .unwrap();
//! // Iterate over each key
//! for key in keys.iter() {
//! println!("Key: 0x{}", hex::encode(&key));
//! }
//! # }
//! ```
//!
//! ## Iterate over Storage
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageClient;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! # // Obtain the storage client wrapper from the API.
//! # let storage: StorageClient<_> = api.client.storage();
//! // Iterate over keys and values.
//! let mut iter = storage
//! .iter::<polkadot::xcm_pallet::storage::VersionNotifiers>(None)
//! .await
//! .unwrap();
//! while let Some((key, value)) = iter.next().await.unwrap() {
//! println!("Key: 0x{}", hex::encode(&key));
//! println!("Value: {}", value);
//! }
//! # }
//! ```
use codec::{
Decode,
Encode,
};
use parking_lot::RwLock;
use sp_core::storage::{
StorageChangeSet,
StorageData,
StorageKey,
};
pub use sp_runtime::traits::SignedExtension;
use std::{
marker::PhantomData,
sync::Arc,
};
use crate::{
error::BasicError,
metadata::{
Metadata,
MetadataError,
},
rpc::Rpc,
Config,
StorageHasher,
};
/// Storage entry trait.
pub trait StorageEntry {
/// Pallet name.
const PALLET: &'static str;
/// Storage name.
const STORAGE: &'static str;
/// Type of the storage entry value.
type Value: Decode;
/// Get the key data for the storage.
fn key(&self) -> StorageEntryKey;
}
/// The prefix of the key to a [`StorageEntry`]
pub struct StorageKeyPrefix(Vec<u8>);
impl StorageKeyPrefix {
/// Create the storage key prefix for a [`StorageEntry`]
pub fn new<T: StorageEntry>() -> Self {
let mut bytes = sp_core::twox_128(T::PALLET.as_bytes()).to_vec();
bytes.extend(&sp_core::twox_128(T::STORAGE.as_bytes())[..]);
Self(bytes)
}
/// Convert the prefix into a [`StorageKey`]
pub fn to_storage_key(self) -> StorageKey {
StorageKey(self.0)
}
}
/// Storage key.
pub enum StorageEntryKey {
/// Plain key.
Plain,
/// Map key(s).
Map(Vec<StorageMapKey>),
}
impl StorageEntryKey {
/// Construct the final [`sp_core::storage::StorageKey`] for the storage entry.
pub fn final_key(&self, prefix: StorageKeyPrefix) -> sp_core::storage::StorageKey {
let mut bytes = prefix.0;
if let Self::Map(map_keys) = self {
for map_key in map_keys {
bytes.extend(Self::hash(&map_key.hasher, &map_key.value))
}
}
sp_core::storage::StorageKey(bytes)
}
fn hash(hasher: &StorageHasher, bytes: &[u8]) -> Vec<u8> {
match hasher {
StorageHasher::Identity => bytes.to_vec(),
StorageHasher::Blake2_128 => sp_core::blake2_128(bytes).to_vec(),
StorageHasher::Blake2_128Concat => {
// copied from substrate Blake2_128Concat::hash since StorageHasher is not public
sp_core::blake2_128(bytes)
.iter()
.chain(bytes)
.cloned()
.collect()
}
StorageHasher::Blake2_256 => sp_core::blake2_256(bytes).to_vec(),
StorageHasher::Twox128 => sp_core::twox_128(bytes).to_vec(),
StorageHasher::Twox256 => sp_core::twox_256(bytes).to_vec(),
StorageHasher::Twox64Concat => {
sp_core::twox_64(bytes)
.iter()
.chain(bytes)
.cloned()
.collect()
}
}
}
}
/// Storage key for a Map.
pub struct StorageMapKey {
value: Vec<u8>,
hasher: StorageHasher,
}
impl StorageMapKey {
/// Create a new [`StorageMapKey`] with the encoded data and the hasher.
pub fn new<T: Encode>(value: &T, hasher: StorageHasher) -> Self {
Self {
value: value.encode(),
hasher,
}
}
}
/// Client for querying runtime storage.
pub struct StorageClient<'a, T: Config> {
rpc: &'a Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
iter_page_size: u32,
}
impl<'a, T: Config> Clone for StorageClient<'a, T> {
fn clone(&self) -> Self {
Self {
rpc: self.rpc,
metadata: Arc::clone(&self.metadata),
iter_page_size: self.iter_page_size,
}
}
}
impl<'a, T: Config> StorageClient<'a, T> {
/// Create a new [`StorageClient`]
pub fn new(
rpc: &'a Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
iter_page_size: u32,
) -> Self {
Self {
rpc,
metadata,
iter_page_size,
}
}
/// Fetch the value under an unhashed storage key
pub async fn fetch_unhashed<V: Decode>(
&self,
key: StorageKey,
hash: Option<T::Hash>,
) -> Result<Option<V>, BasicError> {
if let Some(data) = self.rpc.storage(&key, hash).await? {
Ok(Some(Decode::decode(&mut &data.0[..])?))
} else {
Ok(None)
}
}
/// Fetch the raw encoded value under the raw storage key.
pub async fn fetch_raw(
&self,
key: StorageKey,
hash: Option<T::Hash>,
) -> Result<Option<StorageData>, BasicError> {
self.rpc.storage(&key, hash).await
}
/// Fetch a StorageKey with an optional block hash.
pub async fn fetch<F: StorageEntry>(
&self,
store: &F,
hash: Option<T::Hash>,
) -> Result<Option<F::Value>, BasicError> {
let prefix = StorageKeyPrefix::new::<F>();
let key = store.key().final_key(prefix);
self.fetch_unhashed::<F::Value>(key, hash).await
}
/// Fetch a StorageKey that has a default value with an optional block hash.
pub async fn fetch_or_default<F: StorageEntry>(
&self,
store: &F,
hash: Option<T::Hash>,
) -> Result<F::Value, BasicError> {
if let Some(data) = self.fetch(store, hash).await? {
Ok(data)
} else {
let metadata = self.metadata.read();
let pallet_metadata = metadata.pallet(F::PALLET)?;
let storage_metadata = pallet_metadata.storage(F::STORAGE)?;
let default = Decode::decode(&mut &storage_metadata.default[..])
.map_err(MetadataError::DefaultError)?;
Ok(default)
}
}
/// Query historical storage entries
pub async fn query_storage(
&self,
keys: Vec<StorageKey>,
from: T::Hash,
to: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
self.rpc.query_storage(keys, from, to).await
}
/// Fetch up to `count` keys for a storage map in lexicographic order.
///
/// Supports pagination by passing a value to `start_key`.
pub async fn fetch_keys<F: StorageEntry>(
&self,
count: u32,
start_key: Option<StorageKey>,
hash: Option<T::Hash>,
) -> Result<Vec<StorageKey>, BasicError> {
let key = StorageKeyPrefix::new::<F>().to_storage_key();
let keys = self
.rpc
.storage_keys_paged(Some(key), count, start_key, hash)
.await?;
Ok(keys)
}
/// Returns an iterator of key value pairs.
pub async fn iter<F: StorageEntry>(
&self,
hash: Option<T::Hash>,
) -> Result<KeyIter<'a, T, F>, BasicError> {
let hash = if let Some(hash) = hash {
hash
} else {
self.rpc
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
};
Ok(KeyIter {
client: self.clone(),
hash,
count: self.iter_page_size,
start_key: None,
buffer: Default::default(),
_marker: PhantomData,
})
}
}
/// Iterates over key value pairs in a map.
pub struct KeyIter<'a, T: Config, F: StorageEntry> {
client: StorageClient<'a, T>,
_marker: PhantomData<F>,
count: u32,
hash: T::Hash,
start_key: Option<StorageKey>,
buffer: Vec<(StorageKey, StorageData)>,
}
impl<'a, T: Config, F: StorageEntry> KeyIter<'a, T, F> {
/// Returns the next key value pair from a map.
pub async fn next(&mut self) -> Result<Option<(StorageKey, F::Value)>, BasicError> {
loop {
if let Some((k, v)) = self.buffer.pop() {
return Ok(Some((k, Decode::decode(&mut &v.0[..])?)))
} else {
let keys = self
.client
.fetch_keys::<F>(self.count, self.start_key.take(), Some(self.hash))
.await?;
if keys.is_empty() {
return Ok(None)
}
self.start_key = keys.last().cloned();
let change_sets = self
.client
.rpc
.query_storage_at(&keys, Some(self.hash))
.await?;
for change_set in change_sets {
for (k, v) in change_set.changes {
if let Some(v) = v {
self.buffer.push((k, v));
}
}
}
debug_assert_eq!(self.buffer.len(), keys.len());
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing and working with storage items.
mod storage_address;
mod storage_client;
mod storage_map_key;
pub mod utils;
pub use storage_client::{
KeyIter,
StorageClient,
};
// Re-export as this is used in the public API:
pub use sp_core::storage::StorageKey;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use super::{
storage_address::{
dynamic,
dynamic_root,
DynamicStorageAddress,
StaticStorageAddress,
StorageAddress,
Yes,
},
storage_map_key::{
StorageHasher,
StorageMapKey,
},
};
}
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use storage_address::{
dynamic,
dynamic_root,
DynamicStorageAddress,
StaticStorageAddress,
StorageAddress,
};
+286
View File
@@ -0,0 +1,286 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_map_key::StorageMapKey;
use crate::{
dynamic::{
DecodedValue,
Value,
},
error::{
Error,
StorageAddressError,
},
metadata::{
DecodeWithMetadata,
EncodeWithMetadata,
Metadata,
},
};
use frame_metadata::StorageEntryType;
use scale_info::TypeDef;
use std::borrow::Cow;
// We use this type a bunch, so export it from here.
pub use frame_metadata::StorageHasher;
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
pub trait StorageAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// Can an entry be fetched from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsFetchable;
/// Can a default entry be obtained from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsDefaultable;
/// Can this address be iterated over?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsIterable;
/// The name of the pallet that the entry lives under.
fn pallet_name(&self) -> &str;
/// The name of the entry in a given pallet that the item is at.
fn entry_name(&self) -> &str;
/// Output the non-prefix bytes; that is, any additional bytes that need
/// to be appended to the key to dig into maps.
fn append_entry_bytes(
&self,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error>;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// Used to signal whether a [`StorageAddress`] can be iterated,
/// fetched and returned with a default value in the type system.
pub struct Yes;
/// This represents a statically generated storage lookup address.
pub struct StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: &'static str,
entry_name: &'static str,
// How to access the specific value at that storage address.
storage_entry_keys: Vec<StorageMapKey>,
// Hash provided from static code for validation.
validation_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
}
impl<ReturnTy, Fetchable, Defaultable, Iterable>
StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable>
where
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`StaticStorageAddress`] that will be validated
/// against node metadata using the hash given.
pub fn new(
pallet_name: &'static str,
entry_name: &'static str,
storage_entry_keys: Vec<StorageMapKey>,
hash: [u8; 32],
) -> Self {
Self {
pallet_name,
entry_name,
storage_entry_keys,
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Return bytes representing this storage entry.
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
super::utils::write_storage_address_root_bytes(self, &mut bytes);
for entry in &self.storage_entry_keys {
entry.to_bytes(&mut bytes);
}
bytes
}
/// Return bytes representing the root of this storage entry (ie a hash of
/// the pallet and entry name).
pub fn to_root_bytes(&self) -> Vec<u8> {
super::utils::storage_address_root_bytes(self)
}
}
impl<ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable>
where
ReturnTy: DecodeWithMetadata,
{
type Target = ReturnTy;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
type IsFetchable = Fetchable;
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn entry_name(&self) -> &str {
self.entry_name
}
fn append_entry_bytes(
&self,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
for entry in &self.storage_entry_keys {
entry.to_bytes(bytes);
}
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// This represents a dynamically generated storage address.
pub struct DynamicStorageAddress<'a, Encodable> {
pallet_name: Cow<'a, str>,
entry_name: Cow<'a, str>,
storage_entry_keys: Vec<Encodable>,
}
/// Construct a new dynamic storage lookup to the root of some entry.
pub fn dynamic_root<'a>(
pallet_name: impl Into<Cow<'a, str>>,
entry_name: impl Into<Cow<'a, str>>,
) -> DynamicStorageAddress<'a, Value> {
DynamicStorageAddress {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
storage_entry_keys: vec![],
}
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<'a, Encodable: EncodeWithMetadata>(
pallet_name: impl Into<Cow<'a, str>>,
entry_name: impl Into<Cow<'a, str>>,
storage_entry_keys: Vec<Encodable>,
) -> DynamicStorageAddress<'a, Encodable> {
DynamicStorageAddress {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
storage_entry_keys,
}
}
impl<'a, Encodable> StorageAddress for DynamicStorageAddress<'a, Encodable>
where
Encodable: EncodeWithMetadata,
{
type Target = DecodedValue;
// For dynamic types, we have no static guarantees about any of
// this stuff, so we just allow it and let it fail at runtime:
type IsFetchable = Yes;
type IsDefaultable = Yes;
type IsIterable = Yes;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn append_entry_bytes(
&self,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let storage = pallet.storage(&self.entry_name)?;
match &storage.ty {
StorageEntryType::Plain(_) => {
if !self.storage_entry_keys.is_empty() {
Err(StorageAddressError::WrongNumberOfKeys {
expected: 0,
actual: self.storage_entry_keys.len(),
}
.into())
} else {
Ok(())
}
}
StorageEntryType::Map { hashers, key, .. } => {
let ty = metadata
.resolve_type(key.id())
.ok_or_else(|| StorageAddressError::TypeNotFound(key.id()))?;
// If the key is a tuple, we encode each value to the corresponding tuple type.
// If the key is not a tuple, encode a single value to the key type.
let type_ids = match ty.type_def() {
TypeDef::Tuple(tuple) => {
tuple.fields().iter().map(|f| f.id()).collect()
}
_other => {
vec![key.id()]
}
};
if type_ids.len() != self.storage_entry_keys.len() {
return Err(StorageAddressError::WrongNumberOfKeys {
expected: type_ids.len(),
actual: self.storage_entry_keys.len(),
}
.into())
}
if hashers.len() == 1 {
// One hasher; hash a tuple of all SCALE encoded bytes with the one hash function.
let mut input = Vec::new();
for (key, type_id) in self.storage_entry_keys.iter().zip(type_ids) {
key.encode_with_metadata(type_id, metadata, &mut input)?;
}
super::storage_map_key::hash_bytes(&input, &hashers[0], bytes);
Ok(())
} else if hashers.len() == type_ids.len() {
// A hasher per field; encode and hash each field independently.
for ((key, type_id), hasher) in
self.storage_entry_keys.iter().zip(type_ids).zip(hashers)
{
let mut input = Vec::new();
key.encode_with_metadata(type_id, metadata, &mut input)?;
super::storage_map_key::hash_bytes(&input, hasher, bytes);
}
Ok(())
} else {
// Mismatch; wrong number of hashers/fields.
Err(StorageAddressError::WrongNumberOfHashers {
hashers: hashers.len(),
fields: type_ids.len(),
}
.into())
}
}
}
}
}
+410
View File
@@ -0,0 +1,410 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_address::{
StorageAddress,
Yes,
};
use crate::{
client::{
OfflineClientT,
OnlineClientT,
},
error::Error,
metadata::{
DecodeWithMetadata,
Metadata,
},
Config,
};
use derivative::Derivative;
use frame_metadata::StorageEntryType;
use scale_info::form::PortableForm;
use sp_core::storage::{
StorageData,
StorageKey,
};
use std::{
future::Future,
marker::PhantomData,
};
/// Query the runtime storage.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct StorageClient<T, Client> {
client: Client,
_marker: PhantomData<T>,
}
impl<T, Client> StorageClient<T, Client> {
/// Create a new [`StorageClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomData,
}
}
}
impl<T, Client> StorageClient<T, Client>
where
T: Config,
Client: OfflineClientT<T>,
{
/// Run the validation logic against some storage 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).
/// 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<Address: StorageAddress>(
&self,
address: &Address,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(
address.pallet_name(),
address.entry_name(),
hash,
&self.client.metadata(),
)?;
}
Ok(())
}
}
impl<T, Client> StorageClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Fetch the raw encoded value at the address/key given.
pub fn fetch_raw<'a>(
&self,
key: &'a [u8],
hash: Option<T::Hash>,
) -> impl Future<Output = Result<Option<Vec<u8>>, Error>> + 'a {
let client = self.client.clone();
// Ensure that the returned future doesn't have a lifetime tied to api.storage(),
// which is a temporary thing we'll be throwing away quickly:
async move {
let data = client.rpc().storage(key, hash).await?;
Ok(data.map(|d| d.0))
}
}
/// Fetch a decoded value from storage at a given address and optional block hash.
///
/// # Example
///
/// ```no_run
/// use subxt::{ PolkadotConfig, OnlineClient };
///
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.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()
/// .fetch(&address, None)
/// .await
/// .unwrap();
///
/// println!("Value: {:?}", value);
/// # }
/// ```
pub fn fetch<'a, Address>(
&self,
address: &'a Address,
hash: Option<T::Hash>,
) -> impl Future<
Output = Result<Option<<Address::Target as DecodeWithMetadata>::Target>, Error>,
> + 'a
where
Address: StorageAddress<IsFetchable = Yes> + 'a,
{
let client = self.clone();
async move {
// 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.
client.validate(address)?;
// Look up the return type ID to enable DecodeWithMetadata:
let metadata = client.client.metadata();
let lookup_bytes = super::utils::storage_address_bytes(address, &metadata)?;
if let Some(data) = client
.client
.storage()
.fetch_raw(&lookup_bytes, hash)
.await?
{
let val = <Address::Target as DecodeWithMetadata>::decode_storage_with_metadata(
&mut &*data,
address.pallet_name(),
address.entry_name(),
&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<'a, Address>(
&self,
address: &'a Address,
hash: Option<T::Hash>,
) -> impl Future<Output = Result<<Address::Target as DecodeWithMetadata>::Target, Error>>
+ 'a
where
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'a,
{
let client = self.client.clone();
async move {
let pallet_name = address.pallet_name();
let storage_name = address.entry_name();
// Metadata validation happens via .fetch():
if let Some(data) = client.storage().fetch(address, hash).await? {
Ok(data)
} else {
let metadata = client.metadata();
// We have to dig into metadata already, so no point using the optimised `decode_storage_with_metadata` call.
let pallet_metadata = metadata.pallet(pallet_name)?;
let storage_metadata = pallet_metadata.storage(storage_name)?;
let return_ty_id =
return_type_from_storage_entry_type(&storage_metadata.ty);
let bytes = &mut &storage_metadata.default[..];
let val = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
bytes,
return_ty_id,
&metadata,
)?;
Ok(val)
}
}
}
/// Fetch up to `count` keys for a storage map in lexicographic order.
///
/// Supports pagination by passing a value to `start_key`.
pub fn fetch_keys<'a>(
&self,
key: &'a [u8],
count: u32,
start_key: Option<&'a [u8]>,
hash: Option<T::Hash>,
) -> impl Future<Output = Result<Vec<StorageKey>, Error>> + 'a {
let client = self.client.clone();
async move {
let keys = client
.rpc()
.storage_keys_paged(key, count, start_key, hash)
.await?;
Ok(keys)
}
}
/// Returns an iterator of key value pairs.
///
/// ```no_run
/// use subxt::{ PolkadotConfig, OnlineClient };
///
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.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_root();
///
/// // Iterate over keys and values at that address.
/// let mut iter = api
/// .storage()
/// .iter(address, 10, None)
/// .await
/// .unwrap();
///
/// while let Some((key, value)) = iter.next().await.unwrap() {
/// println!("Key: 0x{}", hex::encode(&key));
/// println!("Value: {}", value);
/// }
/// # }
/// ```
pub fn iter<Address>(
&self,
address: Address,
page_size: u32,
hash: Option<T::Hash>,
) -> impl Future<Output = Result<KeyIter<T, Client, Address::Target>, Error>> + 'static
where
Address: StorageAddress<IsIterable = Yes> + 'static,
{
let client = self.clone();
async move {
// 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.
client.validate(&address)?;
// Fetch a concrete block hash to iterate over. We do this so that if new blocks
// are produced midway through iteration, we continue to iterate at the block
// we started with and not the new block.
let hash = if let Some(hash) = hash {
hash
} else {
client
.client
.rpc()
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
};
let metadata = client.client.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 return_type_id = lookup_storage_return_type(
&metadata,
address.pallet_name(),
address.entry_name(),
)?;
// The root pallet/entry bytes for this storage entry:
let address_root_bytes = super::utils::storage_address_root_bytes(&address);
Ok(KeyIter {
client,
address_root_bytes,
metadata,
return_type_id,
block_hash: hash,
count: page_size,
start_key: None,
buffer: Default::default(),
_marker: std::marker::PhantomData,
})
}
}
}
/// Iterates over key value pairs in a map.
pub struct KeyIter<T: Config, Client, ReturnTy> {
client: StorageClient<T, Client>,
address_root_bytes: Vec<u8>,
return_type_id: u32,
metadata: Metadata,
count: u32,
block_hash: T::Hash,
start_key: Option<StorageKey>,
buffer: Vec<(StorageKey, StorageData)>,
_marker: std::marker::PhantomData<ReturnTy>,
}
impl<'a, T: Config, Client: OnlineClientT<T>, ReturnTy> KeyIter<T, Client, ReturnTy>
where
T: Config,
Client: OnlineClientT<T>,
ReturnTy: DecodeWithMetadata,
{
/// Returns the next key value pair from a map.
pub async fn next(
&mut self,
) -> Result<Option<(StorageKey, ReturnTy::Target)>, Error> {
loop {
if let Some((k, v)) = self.buffer.pop() {
let val = ReturnTy::decode_with_metadata(
&mut &v.0[..],
self.return_type_id,
&self.metadata,
)?;
return Ok(Some((k, val)))
} else {
let start_key = self.start_key.take();
let keys = self
.client
.fetch_keys(
&self.address_root_bytes,
self.count,
start_key.as_ref().map(|k| &*k.0),
Some(self.block_hash),
)
.await?;
if keys.is_empty() {
return Ok(None)
}
self.start_key = keys.last().cloned();
let change_sets = self
.client
.client
.rpc()
.query_storage_at(keys.iter().map(|k| &*k.0), Some(self.block_hash))
.await?;
for change_set in change_sets {
for (k, v) in change_set.changes {
if let Some(v) = v {
self.buffer.push((k, v));
}
}
}
debug_assert_eq!(self.buffer.len(), keys.len());
}
}
}
}
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet_name: &str,
storage_name: &str,
hash: [u8; 32],
metadata: &Metadata,
) -> Result<(), Error> {
let expected_hash = match metadata.storage_hash(pallet_name, storage_name) {
Ok(hash) => hash,
Err(e) => return Err(e.into()),
};
match expected_hash == hash {
true => Ok(()),
false => Err(crate::error::MetadataError::IncompatibleMetadata.into()),
}
}
/// look up a return type ID for some storage entry.
fn lookup_storage_return_type(
metadata: &Metadata,
pallet: &str,
entry: &str,
) -> Result<u32, Error> {
let storage_entry_type = &metadata.pallet(pallet)?.storage(entry)?.ty;
Ok(return_type_from_storage_entry_type(storage_entry_type))
}
/// Fetch the return type out of a [`StorageEntryType`].
fn return_type_from_storage_entry_type(entry: &StorageEntryType<PortableForm>) -> u32 {
match entry {
StorageEntryType::Plain(ty) => ty.id(),
StorageEntryType::Map { value, .. } => value.id(),
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use codec::Encode;
pub use sp_runtime::traits::SignedExtension;
// We use this type a bunch, so export it from here.
pub use frame_metadata::StorageHasher;
/// Storage key for a Map.
#[derive(Clone)]
pub struct StorageMapKey {
value: Vec<u8>,
hasher: StorageHasher,
}
impl StorageMapKey {
/// Create a new [`StorageMapKey`] by pre-encoding static data and pairing it with a hasher.
pub fn new<Encodable: Encode>(
value: Encodable,
hasher: StorageHasher,
) -> StorageMapKey {
Self {
value: value.encode(),
hasher,
}
}
/// Convert this [`StorageMapKey`] into bytes and append them to some existing bytes.
pub fn to_bytes(&self, bytes: &mut Vec<u8>) {
hash_bytes(&self.value, &self.hasher, bytes)
}
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub(super) fn hash_bytes(input: &[u8], hasher: &StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_core::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_core::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_core::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_core::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_core::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_core::twox_64(input));
bytes.extend(input);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! these utility methods complement the [`StorageAddress`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use super::StorageAddress;
use crate::{
error::Error,
metadata::Metadata,
};
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(&sp_core::twox_128(addr.pallet_name().as_bytes()));
out.extend(&sp_core::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
@@ -20,6 +20,9 @@
mod params;
mod signer;
mod tx_client;
mod tx_payload;
mod tx_progress;
pub use self::{
params::{
@@ -38,4 +41,20 @@ pub use self::{
PairSigner,
Signer,
},
tx_client::{
SignedSubmittableExtrinsic,
TxClient,
},
tx_payload::{
dynamic,
DynamicTxPayload,
StaticTxPayload,
TxPayload,
},
tx_progress::{
TxEvents,
TxInBlock,
TxProgress,
TxStatus,
},
};
@@ -2,16 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
utils::Encoded,
Config,
};
use codec::{
Compact,
Encode,
};
use core::fmt::Debug;
use crate::{
Config,
Encoded,
};
use derivative::Derivative;
// We require Era as a param below, so make it available from here.
pub use sp_runtime::generic::Era;
@@ -20,7 +20,7 @@ pub use sp_runtime::generic::Era;
/// "additional" parameters that are signed and used in transactions.
/// see [`BaseExtrinsicParams`] for an implementation that is compatible with
/// a Polkadot node.
pub trait ExtrinsicParams<T: Config>: Debug {
pub trait ExtrinsicParams<Index, Hash>: Debug + 'static {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
@@ -30,8 +30,8 @@ pub trait ExtrinsicParams<T: Config>: Debug {
fn new(
spec_version: u32,
tx_version: u32,
nonce: T::Index,
genesis_hash: T::Hash,
nonce: Index,
genesis_hash: Hash,
other_params: Self::OtherParams,
) -> Self;
@@ -73,7 +73,8 @@ pub type PolkadotExtrinsicParamsBuilder<T> = BaseExtrinsicParamsBuilder<T, Plain
/// If your node differs in the "signed extra" and "additional" parameters expected
/// to be sent/signed with a transaction, then you can define your own type which
/// implements the [`ExtrinsicParams`] trait.
#[derive(Debug)]
#[derive(Derivative)]
#[derivative(Debug(bound = "Tip: Debug"))]
pub struct BaseExtrinsicParams<T: Config, Tip: Debug> {
era: Era,
nonce: T::Index,
@@ -91,6 +92,13 @@ pub struct BaseExtrinsicParams<T: Config, Tip: Debug> {
///
/// Prefer to use [`SubstrateExtrinsicParamsBuilder`] for a version of this tailored towards
/// Substrate, or [`PolkadotExtrinsicParamsBuilder`] for a version tailored to Polkadot.
#[derive(Derivative)]
#[derivative(
Debug(bound = "Tip: Debug"),
Clone(bound = "Tip: Clone"),
Copy(bound = "Tip: Copy"),
PartialEq(bound = "Tip: PartialEq")
)]
pub struct BaseExtrinsicParamsBuilder<T: Config, Tip> {
era: Era,
mortality_checkpoint: Option<T::Hash>,
@@ -132,7 +140,9 @@ impl<T: Config, Tip: Default> Default for BaseExtrinsicParamsBuilder<T, Tip> {
}
}
impl<T: Config, Tip: Debug + Encode> ExtrinsicParams<T> for BaseExtrinsicParams<T, Tip> {
impl<T: Config, Tip: Debug + Encode + 'static> ExtrinsicParams<T::Index, T::Hash>
for BaseExtrinsicParams<T, Tip>
{
type OtherParams = BaseExtrinsicParamsBuilder<T, Tip>;
fn new(
+331
View File
@@ -0,0 +1,331 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TxPayload;
use crate::{
client::{
OfflineClientT,
OnlineClientT,
},
error::Error,
tx::{
ExtrinsicParams,
Signer,
TxProgress,
},
utils::{
Encoded,
PhantomDataSendSync,
},
Config,
};
use codec::{
Compact,
Encode,
};
use derivative::Derivative;
use sp_runtime::{
traits::Hash,
ApplyExtrinsicResult,
};
/// A client for working with transactions.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct TxClient<T: Config, Client> {
client: Client,
_marker: PhantomDataSendSync<T>,
}
impl<T: Config, Client> TxClient<T, Client> {
/// Create a new [`TxClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomDataSendSync::new(),
}
}
}
impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
/// 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>
where
Call: TxPayload,
{
if let Some(actual_hash) = call.validation_hash() {
let metadata = self.client.metadata();
let expected_hash =
metadata.call_hash(call.pallet_name(), call.call_name())?;
if actual_hash != expected_hash {
return Err(crate::metadata::MetadataError::IncompatibleMetadata.into())
}
}
Ok(())
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
where
Call: TxPayload,
{
let metadata = self.client.metadata();
let mut bytes = Vec::new();
call.encode_call_data(&metadata, &mut bytes)?;
Ok(bytes)
}
/// Creates a raw signed extrinsic, without submitting it.
pub async fn create_signed_with_nonce<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
account_nonce: T::Index,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<SignedSubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = Encoded(self.call_data(call)?);
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = {
// Obtain spec version and transaction version from the runtime version of the client.
let runtime = self.client.runtime_version();
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::new(
runtime.spec_version,
runtime.transaction_version,
account_nonce,
self.client.genesis_hash(),
other_params,
)
};
tracing::debug!(
"tx additional_and_extra_params: {:?}",
additional_and_extra_params
);
// 4. Construct signature. This is compatible with the Encode impl
// for SignedPayload (which is this payload of bytes that we'd like)
// to sign. See:
// https://github.com/paritytech/substrate/blob/9a6d706d8db00abb6ba183839ec98ecd9924b1f8/primitives/runtime/src/generic/unchecked_extrinsic.rs#L215)
let signature = {
let mut bytes = Vec::new();
call_data.encode_to(&mut bytes);
additional_and_extra_params.encode_extra_to(&mut bytes);
additional_and_extra_params.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
signer.sign(&sp_core::blake2_256(&bytes))
} else {
signer.sign(&bytes)
}
};
tracing::debug!("tx signature: {}", hex::encode(signature.encode()));
// 5. Encode extrinsic, now that we have the parts we need. This is compatible
// with the Encode impl for UncheckedExtrinsic (protocol version 4).
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
signer.address().encode_to(&mut encoded_inner);
// the signature bytes
signature.encode_to(&mut encoded_inner);
// attach custom extra params
additional_and_extra_params.encode_extra_to(&mut encoded_inner);
// and now, call data
call_data.encode_to(&mut encoded_inner);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
.expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
// maybe we can just return the raw bytes..
Ok(SignedSubmittableExtrinsic {
client: self.client.clone(),
encoded: Encoded(extrinsic),
marker: std::marker::PhantomData,
})
}
}
impl<T: Config, C: OnlineClientT<T>> TxClient<T, C> {
/// Creates a raw signed extrinsic, without submitting it.
pub async fn create_signed<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<SignedSubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
{
// Get nonce from the node.
let account_nonce = if let Some(nonce) = signer.nonce() {
nonce
} else {
self.client
.rpc()
.system_account_next_index(signer.account_id())
.await?
};
self.create_signed_with_nonce(call, signer, account_nonce, other_params)
.await
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
///
/// 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 sign_and_submit_then_watch_default<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
{
self.sign_and_submit_then_watch(call, signer, Default::default())
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
///
/// 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 sign_and_submit_then_watch<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
{
self.create_signed(call, signer, other_params)
.await?
.submit_and_watch()
.await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<T::Hash, Error>
where
Call: TxPayload,
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
{
self.sign_and_submit(call, signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
{
self.create_signed(call, signer, other_params)
.await?
.submit()
.await
}
}
/// This represents an extrinsic that has been signed and is ready to submit.
pub struct SignedSubmittableExtrinsic<T, C> {
client: C,
encoded: Encoded,
marker: std::marker::PhantomData<T>,
}
impl<T, C> SignedSubmittableExtrinsic<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
/// Submits the extrinsic to the chain.
///
/// 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> {
// Get a hash of the extrinsic (we'll need this later).
let ext_hash = T::Hashing::hash_of(&self.encoded);
// Submit and watch for transaction progress.
let sub = self.client.rpc().watch_extrinsic(&self.encoded).await?;
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
}
/// Submits the extrinsic to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn submit(&self) -> Result<T::Hash, Error> {
self.client.rpc().submit_extrinsic(&self.encoded).await
}
/// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
///
/// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
pub async fn dry_run(
&self,
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, Error> {
self.client.rpc().dry_run(self.encoded(), at).await
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
}
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::{
dynamic::Value,
error::{
Error,
MetadataError,
},
metadata::Metadata,
};
use codec::Encode;
use std::borrow::Cow;
/// This represents a transaction payload that can be submitted
/// to a node.
pub trait TxPayload {
/// The name of the pallet that the call lives under.
fn pallet_name(&self) -> &str;
/// The name of the call.
fn call_name(&self) -> &str;
/// Encode call data to the provided output.
fn encode_call_data(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error>;
/// An optional validation hash that can be provided
/// to verify that the shape of the call on the node
/// aligns with our expectations.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents a statically generated transaction payload.
pub struct StaticTxPayload<CallData> {
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
}
impl<CallData> StaticTxPayload<CallData> {
/// Create a new [`StaticTxPayload`] from static data.
pub fn new(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
StaticTxPayload {
pallet_name,
call_name,
call_data,
validation_hash: Some(validation_hash),
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
}
impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn call_name(&self) -> &str {
self.call_name
}
fn encode_call_data(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(self.pallet_name)?;
let pallet_index = pallet.index();
let call_index = pallet.call_index(self.call_name)?;
pallet_index.encode_to(out);
call_index.encode_to(out);
self.call_data.encode_to(out);
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// This represents a dynamically generated transaction payload.
pub struct DynamicTxPayload<'a> {
pallet_name: Cow<'a, str>,
call_name: Cow<'a, str>,
fields: Vec<Value<()>>,
}
/// Construct a new dynamic transaction payload to submit to a node.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
call_name: impl Into<Cow<'a, str>>,
fields: Vec<Value<()>>,
) -> DynamicTxPayload<'a> {
DynamicTxPayload {
pallet_name: pallet_name.into(),
call_name: call_name.into(),
fields,
}
}
impl<'a> TxPayload for DynamicTxPayload<'a> {
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn call_name(&self) -> &str {
&self.call_name
}
fn encode_call_data(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let call_id = pallet.call_ty_id().ok_or(MetadataError::CallNotFound)?;
let call_value =
Value::unnamed_variant(self.call_name.to_owned(), self.fields.clone());
pallet.index().encode_to(out);
scale_value::scale::encode_as_type(&call_value, call_id, metadata.types(), out)?;
Ok(())
}
}
@@ -2,32 +2,27 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types representing extrinsics/transactions that have been submitted to a node.
use std::task::Poll;
use crate::PhantomDataSendSync;
use codec::Decode;
use sp_runtime::traits::Hash;
pub use sp_runtime::traits::SignedExtension;
use crate::{
client::Client,
client::OnlineClientT,
error::{
BasicError,
DispatchError,
Error,
HasModuleError,
ModuleError,
RuntimeError,
TransactionError,
},
events::{
self,
EventDetails,
Events,
RawEventDetails,
EventsClient,
Phase,
StaticEvent,
},
rpc::SubstrateTransactionStatus,
rpc::SubstrateTxStatus,
Config,
Phase,
};
use derivative::Derivative;
use futures::{
@@ -38,71 +33,65 @@ use jsonrpsee::core::{
client::Subscription as RpcSubscription,
Error as RpcError,
};
use sp_runtime::traits::Hash;
/// This struct represents a subscription to the progress of some transaction, and is
/// returned from [`crate::SubmittableExtrinsic::sign_and_submit_then_watch()`].
pub use sp_runtime::traits::SignedExtension;
/// This struct represents a subscription to the progress of some transaction.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionProgress<'client, T: Config, E, Evs> {
sub: Option<RpcSubscription<SubstrateTransactionStatus<T::Hash, T::Hash>>>,
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
pub struct TxProgress<T: Config, C> {
sub: Option<RpcSubscription<SubstrateTxStatus<T::Hash, T::Hash>>>,
ext_hash: T::Hash,
client: &'client Client<T>,
_error: PhantomDataSendSync<(E, Evs)>,
client: C,
}
// The above type is not `Unpin` by default unless the generic param `T` is,
// so we manually make it clear that Unpin is actually fine regardless of `T`
// (we don't care if this moves around in memory while it's "pinned").
impl<'client, T: Config, E, Evs> Unpin for TransactionProgress<'client, T, E, Evs> {}
impl<T: Config, C> Unpin for TxProgress<T, C> {}
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
TransactionProgress<'client, T, E, Evs>
{
/// Instantiate a new [`TransactionProgress`] from a custom subscription.
impl<T: Config, C> TxProgress<T, C> {
/// Instantiate a new [`TxProgress`] from a custom subscription.
pub fn new(
sub: RpcSubscription<SubstrateTransactionStatus<T::Hash, T::Hash>>,
client: &'client Client<T>,
sub: RpcSubscription<SubstrateTxStatus<T::Hash, T::Hash>>,
client: C,
ext_hash: T::Hash,
) -> Self {
Self {
sub: Some(sub),
client,
ext_hash,
_error: PhantomDataSendSync::new(),
}
}
}
impl<T: Config, C: OnlineClientT<T>> TxProgress<T, C> {
/// Return the next transaction status when it's emitted. This just delegates to the
/// [`futures::Stream`] implementation for [`TransactionProgress`], but allows you to
/// [`futures::Stream`] implementation for [`TxProgress`], but allows you to
/// avoid importing that trait if you don't otherwise need it.
pub async fn next_item(
&mut self,
) -> Option<Result<TransactionStatus<'client, T, E, Evs>, BasicError>> {
pub async fn next_item(&mut self) -> Option<Result<TxStatus<T, C>, Error>> {
self.next().await
}
/// Wait for the transaction to be in a block (but not necessarily finalized), and return
/// an [`TransactionInBlock`] instance when this happens, or an error if there was a problem
/// an [`TxInBlock`] instance when this happens, or an error if there was a problem
/// waiting for this to happen.
///
/// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the
/// transaction progresses, use [`TransactionProgress::next_item()`] instead.
/// transaction progresses, use [`TxProgress::next_item()`] instead.
///
/// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they
/// may well indicate with some probability that the transaction will not make it into a block,
/// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower
/// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_in_block(
mut self,
) -> Result<TransactionInBlock<'client, T, E, Evs>, BasicError> {
/// level [`TxProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_in_block(mut self) -> Result<TxInBlock<T, C>, Error> {
while let Some(status) = self.next_item().await {
match status? {
// Finalized or otherwise in a block! Return.
TransactionStatus::InBlock(s) | TransactionStatus::Finalized(s) => {
return Ok(s)
}
TxStatus::InBlock(s) | TxStatus::Finalized(s) => return Ok(s),
// Error scenarios; return the error.
TransactionStatus::FinalityTimeout(_) => {
TxStatus::FinalityTimeout(_) => {
return Err(TransactionError::FinalitySubscriptionTimeout.into())
}
// Ignore anything else and wait for next status event:
@@ -112,25 +101,23 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
Err(RpcError::Custom("RPC subscription dropped".into()).into())
}
/// Wait for the transaction to be finalized, and return a [`TransactionInBlock`]
/// Wait for the transaction to be finalized, and return a [`TxInBlock`]
/// instance when it is, or an error if there was a problem waiting for finalization.
///
/// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the
/// transaction progresses, use [`TransactionProgress::next_item()`] instead.
/// transaction progresses, use [`TxProgress::next_item()`] instead.
///
/// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they
/// may well indicate with some probability that the transaction will not make it into a block,
/// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower
/// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized(
mut self,
) -> Result<TransactionInBlock<'client, T, E, Evs>, BasicError> {
/// level [`TxProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized(mut self) -> Result<TxInBlock<T, C>, Error> {
while let Some(status) = self.next_item().await {
match status? {
// Finalized! Return.
TransactionStatus::Finalized(s) => return Ok(s),
TxStatus::Finalized(s) => return Ok(s),
// Error scenarios; return the error.
TransactionStatus::FinalityTimeout(_) => {
TxStatus::FinalityTimeout(_) => {
return Err(TransactionError::FinalitySubscriptionTimeout.into())
}
// Ignore and wait for next status event:
@@ -145,24 +132,20 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
/// as well as a couple of other details (block hash and extrinsic hash).
///
/// **Note:** consumes self. If you'd like to perform multiple actions as progress is made,
/// use [`TransactionProgress::next_item()`] instead.
/// use [`TxProgress::next_item()`] instead.
///
/// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they
/// may well indicate with some probability that the transaction will not make it into a block,
/// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower
/// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized_success(
self,
) -> Result<TransactionEvents<T, Evs>, Error<E>> {
/// level [`TxProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized_success(self) -> Result<TxEvents<T>, Error> {
let evs = self.wait_for_finalized().await?.wait_for_success().await?;
Ok(evs)
}
}
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
for TransactionProgress<'client, T, E, Evs>
{
type Item = Result<TransactionStatus<'client, T, E, Evs>, BasicError>;
impl<T: Config, C: OnlineClientT<T>> Stream for TxProgress<T, C> {
type Item = Result<TxStatus<T, C>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
@@ -177,28 +160,22 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
.map_err(|e| e.into())
.map_ok(|status| {
match status {
SubstrateTransactionStatus::Future => TransactionStatus::Future,
SubstrateTransactionStatus::Ready => TransactionStatus::Ready,
SubstrateTransactionStatus::Broadcast(peers) => {
TransactionStatus::Broadcast(peers)
}
SubstrateTransactionStatus::InBlock(hash) => {
TransactionStatus::InBlock(TransactionInBlock::new(
SubstrateTxStatus::Future => TxStatus::Future,
SubstrateTxStatus::Ready => TxStatus::Ready,
SubstrateTxStatus::Broadcast(peers) => TxStatus::Broadcast(peers),
SubstrateTxStatus::InBlock(hash) => {
TxStatus::InBlock(TxInBlock::new(
hash,
self.ext_hash,
self.client,
self.client.clone(),
))
}
SubstrateTransactionStatus::Retracted(hash) => {
TransactionStatus::Retracted(hash)
}
SubstrateTransactionStatus::Usurped(hash) => {
TransactionStatus::Usurped(hash)
}
SubstrateTransactionStatus::Dropped => TransactionStatus::Dropped,
SubstrateTransactionStatus::Invalid => TransactionStatus::Invalid,
SubstrateTxStatus::Retracted(hash) => TxStatus::Retracted(hash),
SubstrateTxStatus::Usurped(hash) => TxStatus::Usurped(hash),
SubstrateTxStatus::Dropped => TxStatus::Dropped,
SubstrateTxStatus::Invalid => TxStatus::Invalid,
// Only the following statuses are actually considered "final" (see the substrate
// docs on `TransactionStatus`). Basically, either the transaction makes it into a
// docs on `TxStatus`). Basically, either the transaction makes it into a
// block, or we eventually give up on waiting for it to make it into a block.
// Even `Dropped`/`Invalid`/`Usurped` transactions might make it into a block eventually.
//
@@ -206,16 +183,16 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
// nonce might still be valid on some fork on another node which ends up being finalized.
// Equally, a transaction `Dropped` from one node may still be in the transaction pool,
// and make it into a block, on another node. Likewise with `Usurped`.
SubstrateTransactionStatus::FinalityTimeout(hash) => {
SubstrateTxStatus::FinalityTimeout(hash) => {
self.sub = None;
TransactionStatus::FinalityTimeout(hash)
TxStatus::FinalityTimeout(hash)
}
SubstrateTransactionStatus::Finalized(hash) => {
SubstrateTxStatus::Finalized(hash) => {
self.sub = None;
TransactionStatus::Finalized(TransactionInBlock::new(
TxStatus::Finalized(TxInBlock::new(
hash,
self.ext_hash,
self.client,
self.client.clone(),
))
}
}
@@ -223,12 +200,12 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
}
}
//* Dev note: The below is adapted from the substrate docs on `TransactionStatus`, which this
//* enum was adapted from (and which is an exact copy of `SubstrateTransactionStatus` in this crate).
//* Dev note: The below is adapted from the substrate docs on `TxStatus`, which this
//* enum was adapted from (and which is an exact copy of `SubstrateTxStatus` in this crate).
//* Note that the number of finality watchers is, at the time of writing, found in the constant
//* `MAX_FINALITY_WATCHERS` in the `sc_transaction_pool` crate.
//*
/// Possible transaction statuses returned from our [`TransactionProgress::next_item()`] call.
/// Possible transaction statuses returned from our [`TxProgress::next_item()`] call.
///
/// These status events can be grouped based on their kinds as:
///
@@ -270,8 +247,8 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
/// within 512 blocks. This either indicates that finality is not available for your chain,
/// or that finality gadget is lagging behind.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
pub enum TxStatus<T: Config, C> {
/// The transaction is part of the "future" queue.
Future,
/// The transaction is part of the "ready" queue.
@@ -279,7 +256,7 @@ pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
/// The transaction has been broadcast to the given peers.
Broadcast(Vec<String>),
/// The transaction has been included in a block with given hash.
InBlock(TransactionInBlock<'client, T, E, Evs>),
InBlock(TxInBlock<T, C>),
/// The block this transaction was included in has been retracted,
/// probably because it did not make it onto the blocks which were
/// finalized.
@@ -288,7 +265,7 @@ pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
/// blocks, and so the subscription has ended.
FinalityTimeout(T::Hash),
/// The transaction has been finalized by a finality-gadget, e.g GRANDPA.
Finalized(TransactionInBlock<'client, T, E, Evs>),
Finalized(TxInBlock<T, C>),
/// The transaction has been replaced in the pool by another transaction
/// that provides the same tags. (e.g. same (sender, nonce)).
Usurped(T::Hash),
@@ -298,10 +275,10 @@ pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
Invalid,
}
impl<'client, T: Config, E: Decode, Evs: Decode> TransactionStatus<'client, T, E, Evs> {
impl<T: Config, C> TxStatus<T, C> {
/// A convenience method to return the `Finalized` details. Returns
/// [`None`] if the enum variant is not [`TransactionStatus::Finalized`].
pub fn as_finalized(&self) -> Option<&TransactionInBlock<'client, T, E, Evs>> {
/// [`None`] if the enum variant is not [`TxStatus::Finalized`].
pub fn as_finalized(&self) -> Option<&TxInBlock<T, C>> {
match self {
Self::Finalized(val) => Some(val),
_ => None,
@@ -309,8 +286,8 @@ impl<'client, T: Config, E: Decode, Evs: Decode> TransactionStatus<'client, T, E
}
/// A convenience method to return the `InBlock` details. Returns
/// [`None`] if the enum variant is not [`TransactionStatus::InBlock`].
pub fn as_in_block(&self) -> Option<&TransactionInBlock<'client, T, E, Evs>> {
/// [`None`] if the enum variant is not [`TxStatus::InBlock`].
pub fn as_in_block(&self) -> Option<&TxInBlock<T, C>> {
match self {
Self::InBlock(val) => Some(val),
_ => None,
@@ -320,27 +297,19 @@ impl<'client, T: Config, E: Decode, Evs: Decode> TransactionStatus<'client, T, E
/// This struct represents a transaction that has made it into a block.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionInBlock<'client, T: Config, E: Decode, Evs: Decode> {
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
pub struct TxInBlock<T: Config, C> {
block_hash: T::Hash,
ext_hash: T::Hash,
client: &'client Client<T>,
_error: PhantomDataSendSync<(E, Evs)>,
client: C,
}
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
TransactionInBlock<'client, T, E, Evs>
{
pub(crate) fn new(
block_hash: T::Hash,
ext_hash: T::Hash,
client: &'client Client<T>,
) -> Self {
impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
pub(crate) fn new(block_hash: T::Hash, ext_hash: T::Hash, client: C) -> Self {
Self {
block_hash,
ext_hash,
client,
_error: PhantomDataSendSync::new(),
}
}
@@ -362,34 +331,21 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
/// **Note:** If multiple `ExtrinsicFailed` errors are returned (for instance
/// because a pallet chooses to emit one as an event, which is considered
/// abnormal behaviour), it is not specified which of the errors is returned here.
/// You can use [`TransactionInBlock::fetch_events`] instead if you'd like to
/// You can use [`TxInBlock::fetch_events`] instead if you'd like to
/// work with multiple "error" events.
///
/// **Note:** This has to download block details from the node and decode events
/// from them.
pub async fn wait_for_success(&self) -> Result<TransactionEvents<T, Evs>, Error<E>> {
pub async fn wait_for_success(&self) -> Result<TxEvents<T>, Error> {
let events = self.fetch_events().await?;
// Try to find any errors; return the first one we encounter.
for ev in events.iter_raw() {
for ev in events.iter() {
let ev = ev?;
if &ev.pallet == "System" && &ev.variant == "ExtrinsicFailed" {
let dispatch_error = E::decode(&mut &*ev.bytes)?;
if let Some(error_data) = dispatch_error.module_error_data() {
// Error index is utilized as the first byte from the error array.
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
let details = metadata
.error(error_data.pallet_index, error_data.error_index())?;
return Err(Error::Module(ModuleError {
pallet: details.pallet().to_string(),
error: details.error().to_string(),
description: details.description().to_vec(),
error_data,
}))
} else {
return Err(Error::Runtime(RuntimeError(dispatch_error)))
}
if ev.pallet_name() == "System" && ev.variant_name() == "ExtrinsicFailed" {
let dispatch_error =
DispatchError::decode_from(ev.field_bytes(), &self.client.metadata());
return Err(dispatch_error.into())
}
}
@@ -402,13 +358,13 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
///
/// **Note:** This has to download block details from the node and decode events
/// from them.
pub async fn fetch_events(&self) -> Result<TransactionEvents<T, Evs>, BasicError> {
pub async fn fetch_events(&self) -> Result<TxEvents<T>, Error> {
let block = self
.client
.rpc()
.block(Some(self.block_hash))
.await?
.ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?;
.ok_or(Error::Transaction(TransactionError::BlockHashNotFound))?;
let extrinsic_idx = block.block.extrinsics
.iter()
@@ -418,11 +374,13 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
})
// If we successfully obtain the block hash we think contains our
// extrinsic, the extrinsic should be in there somewhere..
.ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?;
.ok_or(Error::Transaction(TransactionError::BlockHashNotFound))?;
let events = events::at::<T, Evs>(self.client, self.block_hash).await?;
let events = EventsClient::new(self.client.clone())
.at(Some(self.block_hash))
.await?;
Ok(TransactionEvents {
Ok(TxEvents {
ext_hash: self.ext_hash,
ext_idx: extrinsic_idx as u32,
events,
@@ -434,13 +392,13 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
/// We can iterate over the events, or look for a specific one.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionEvents<T: Config, Evs: Decode> {
pub struct TxEvents<T: Config> {
ext_hash: T::Hash,
ext_idx: u32,
events: Events<T, Evs>,
events: Events<T>,
}
impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
impl<T: Config> TxEvents<T> {
/// Return the hash of the block that the transaction has made it into.
pub fn block_hash(&self) -> T::Hash {
self.events.block_hash()
@@ -452,34 +410,18 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
}
/// Return all of the events in the block that the transaction made it into.
pub fn all_events_in_block(&self) -> &events::Events<T, Evs> {
pub fn all_events_in_block(&self) -> &events::Events<T> {
&self.events
}
/// Iterate over the statically decoded events associated with this transaction.
///
/// 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<EventDetails<Evs>, BasicError>> + '_ {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase == Phase::ApplyExtrinsic(self.ext_idx))
.unwrap_or(true) // Keep any errors
})
}
/// Iterate over all of the raw events associated with this transaction.
///
/// This works in the same way that [`events::Events::iter_raw()`] does, with the
/// 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_raw(
&self,
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + '_ {
self.events.iter_raw().filter(|ev| {
pub fn iter(&self) -> impl Iterator<Item = Result<EventDetails, Error>> + '_ {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase == Phase::ApplyExtrinsic(self.ext_idx))
.map(|ev| ev.phase() == Phase::ApplyExtrinsic(self.ext_idx))
.unwrap_or(true) // Keep any errors.
})
}
@@ -488,10 +430,8 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
///
/// 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: crate::Event>(
&self,
) -> impl Iterator<Item = Result<Ev, BasicError>> + '_ {
self.iter_raw().filter_map(|ev| {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
@@ -502,7 +442,7 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
///
/// 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: crate::Event>(&self) -> Result<Option<Ev>, BasicError> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
@@ -510,7 +450,7 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
///
/// 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: crate::Event>(&self) -> Result<bool, BasicError> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
-126
View File
@@ -1,126 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Perform runtime updates in the background using [UpdateClient].
//!
//! There are cases when the node would perform a runtime update. As a result, the subxt's metadata
//! would be out of sync and the API would not be able to submit valid extrinsics.
//! This API keeps the `RuntimeVersion` and `Metadata` of the client synced with the target node.
//!
//! The runtime update is recommended for long-running clients, or for cases where manually
//! restarting subxt would not be feasible. Even with this, extrinsics submitted during a node
//! runtime update are at risk or failing, as it will take `subxt` a moment to catch up.
//!
//! ## Note
//!
//! Here we use tokio to check for updates in the background, but any runtime can be used.
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig};
//! #
//! # #[tokio::main]
//! # async fn main() {
//! # let client = ClientBuilder::new()
//! # .set_url("wss://rpc.polkadot.io:443")
//! # .build::<DefaultConfig>()
//! # .await
//! # .unwrap();
//! #
//! let update_client = client.updates();
//! // Spawn a new background task to handle runtime updates.
//! tokio::spawn(async move {
//! let result = update_client.perform_runtime_updates().await;
//! println!("Runtime update finished with result={:?}", result);
//! });
//! # }
//! ```
use crate::{
rpc::{
Rpc,
RuntimeVersion,
},
BasicError,
Config,
Metadata,
};
use parking_lot::RwLock;
use std::sync::Arc;
/// Client wrapper for performing runtime updates.
pub struct UpdateClient<T: Config> {
rpc: Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
runtime_version: Arc<RwLock<RuntimeVersion>>,
}
impl<T: Config> UpdateClient<T> {
/// Create a new [`UpdateClient`].
pub fn new(
rpc: Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
runtime_version: Arc<RwLock<RuntimeVersion>>,
) -> Self {
Self {
rpc,
metadata,
runtime_version,
}
}
/// Performs runtime updates indefinitely unless encountering an error.
///
/// *Note:* This should be called from a dedicated background task.
pub async fn perform_runtime_updates(&self) -> Result<(), BasicError> {
// Obtain an update subscription to further detect changes in the runtime version of the node.
let mut update_subscription = self.rpc.subscribe_runtime_version().await?;
while let Some(update_runtime_version) = update_subscription.next().await {
// The Runtime Version obtained via subscription.
let update_runtime_version = update_runtime_version?;
// To ensure there are no races between:
// - starting the subxt::Client (fetching runtime version / metadata)
// - subscribing to the runtime updates
// the node provides its runtime version immediately after subscribing.
//
// In those cases, set the Runtime Version on the client if and only if
// the provided runtime version is different than what the client currently
// has stored.
{
// The Runtime Version of the client, as set during building the client
// or during updates.
let runtime_version = self.runtime_version.read();
if runtime_version.spec_version == update_runtime_version.spec_version {
tracing::debug!(
"Runtime update not performed for spec_version={}, client has spec_version={}",
update_runtime_version.spec_version, runtime_version.spec_version
);
continue
}
}
// Update the RuntimeVersion first.
{
let mut runtime_version = self.runtime_version.write();
// Update both the `RuntimeVersion` and `Metadata` of the client.
tracing::info!(
"Performing runtime update from {} to {}",
runtime_version.spec_version,
update_runtime_version.spec_version,
);
*runtime_version = update_runtime_version;
}
// Fetch the new metadata of the runtime node.
let update_metadata = self.rpc.metadata().await?;
tracing::debug!("Performing metadata update");
let mut metadata = self.metadata.write();
*metadata = update_metadata;
tracing::debug!("Runtime update completed");
}
Ok(())
}
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Miscellaneous utility helpers.
use codec::{
Decode,
DecodeAll,
Encode,
};
use derivative::Derivative;
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Encoded(pub Vec<u8>);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec<u8> {
self.0.to_owned()
}
}
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
///
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
/// access the real type `T` [`Self::try_decode`] needs to be used.
#[derive(Derivative, Encode, Decode)]
#[derivative(
Debug(bound = ""),
Clone(bound = ""),
PartialEq(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T: Decode> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T> {
T::decode_all(&mut &self.data[..]).ok()
}
/// Returns the length of the encoded `T`.
pub fn encoded_len(&self) -> usize {
self.data.len()
}
/// Returns the encoded data.
pub fn encoded(&self) -> &[u8] {
&self.data
}
/// Create from the given encoded `data`.
pub fn from_encoded(data: Vec<u8>) -> Self {
Self {
data,
_phantom: PhantomDataSendSync::new(),
}
}
}
/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).
#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
#[scale_info(skip_type_params(T))]
#[doc(hidden)]
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
impl<T> PhantomDataSendSync<T> {
pub(crate) fn new() -> Self {
Self(core::marker::PhantomData)
}
}
unsafe impl<T> Send for PhantomDataSendSync<T> {}
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// This represents a key-value collection and is SCALE compatible
/// with collections like BTreeMap. This has the same type params
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;