mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 12:11:09 +00:00
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:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
>,
|
||||
>();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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::*;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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)>;
|
||||
Reference in New Issue
Block a user