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

* WIP API changes

* debug impls

* Get main crate compiling with first round of changes

* Some tidy up

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

* move transaction into extrinsic folder

* Add runtime updates back to OnlineClient

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

* add support for events to Client

* tidy dupe trait bound

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

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

* First pass updating calls and constants codegen

* WIP storage client updates

* First pass migrated runtime storage over to new format

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

* don't need a Call trait any more

* shuffle things around a bit

* Various proc_macro fixes to get 'cargo check' working

* organise what's exposed from subxt

* Get first example working; balance_transfer_with_params

* get balance_transfer example compiling

* get concurrent_storage_requests.rs example compiling

* get fetch_all_accounts example compiling

* get a bunch more of the examples compiling

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

* wee tweaks

* move StorageAddress to separate file

* pass Defaultable/Iterable info to StorageAddress in codegen

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

* Remove static iteration and strip a generic param from everything

* fix doc tests in subxt crate

* update test utils and start fixing frame tests

* fix frame staking tests

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

* cargo fmt

* remove extra logging during tests

* Appease clippy and no more need for into_iter on events

* cargo fmt

* fix dryRun tests by waiting for blocks

* wait for blocks instead of sleeping or other test hacks

* cargo fmt

* Fix doc links

* Traitify StorageAddress

* remove out-of-date doc comments

* optimise decoding storage a little

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

* clippy fixes

* fix doc links

* fix doc example

* constant address trait for consistency

* fix a typo and remove EncodeWithMetadata stuff

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

* fix docs

* tweak StorageAddress docs

* re-export StorageAddress at root for consistency

* fix clippy things

* Add support for dynamic values

* fix double encoding of storage map key after refactor

* clippy fix

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

* bump scale_value version

* cargo fmt

* Tweak event bits

* cargo fmt

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

* remove unnecessary vec from dynamic example

* Various typo/grammar fixes

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

* Address PR nits

* Undo accidental rename in changelog

* Small PR nits/tidyups

* fix tests; codegen change against latest substrate

* tweak storage address util names

* move error decoding to DecodeError and expose

* impl some basic traits on the extrinsic param builder

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
James Wilson
2022-08-08 11:55:20 +01:00
committed by GitHub
parent 7a09ac6cd7
commit e48f0e3b1d
84 changed files with 23097 additions and 35863 deletions
Binary file not shown.
+43 -80
View File
@@ -22,31 +22,8 @@ use quote::{
};
use scale_info::form::PortableForm;
/// Generate calls from the provided pallet's metadata.
///
/// The function creates a new module named `calls` under the pallet's module.
/// ```ignore
/// pub mod PalletName {
/// pub mod calls {
/// ...
/// }
/// }
/// ```
///
/// The function generates the calls as rust structs that implement the `subxt::Call` trait
/// to uniquely identify the call's identity when creating the extrinsic.
///
/// ```ignore
/// pub struct CallName {
/// pub call_param: type,
/// }
/// impl ::subxt::Call for CallName {
/// ...
/// }
/// ```
///
/// Calls are extracted from the API and wrapped into the generated `TransactionApi` of
/// each module.
/// Generate calls from the provided pallet's metadata. Each call returns a `StaticTxPayload`
/// that can be passed to the subxt client to submit/sign/encode.
///
/// # Arguments
///
@@ -76,35 +53,42 @@ pub fn generate_calls(
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
.iter_mut()
.map(|(variant_name, struct_def)| {
let (call_fn_args, call_args): (Vec<_>, Vec<_>) =
match struct_def.fields {
CompositeDefFields::Named(ref named_fields) => {
named_fields
.iter()
.map(|(name, field)| {
let fn_arg_type = &field.type_path;
let call_arg = if field.is_boxed() {
quote! { #name: ::std::boxed::Box::new(#name) }
} else {
quote! { #name }
};
(quote!( #name: #fn_arg_type ), call_arg)
})
.unzip()
}
CompositeDefFields::NoFields => Default::default(),
CompositeDefFields::Unnamed(_) =>
abort_call_site!(
"Call variant for type {} must have all named fields",
call.ty.id()
)
};
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match struct_def.fields {
CompositeDefFields::Named(ref named_fields) => {
named_fields
.iter()
.map(|(name, field)| {
let fn_arg_type = &field.type_path;
let call_arg = if field.is_boxed() {
quote! { #name: ::std::boxed::Box::new(#name) }
} else {
quote! { #name }
};
(quote!( #name: #fn_arg_type ), call_arg)
})
.unzip()
}
CompositeDefFields::NoFields => Default::default(),
CompositeDefFields::Unnamed(_) => {
abort_call_site!(
"Call variant for type {} must have all named fields",
call.ty.id()
)
}
};
let pallet_name = &pallet.name;
let call_name = &variant_name;
let struct_name = &struct_def.name;
let call_hash = subxt_metadata::get_call_hash(metadata, pallet_name, call_name)
.unwrap_or_else(|_| abort_call_site!("Metadata information for the call {}_{} could not be found", pallet_name, call_name));
let call_hash =
subxt_metadata::get_call_hash(metadata, pallet_name, call_name)
.unwrap_or_else(|_| {
abort_call_site!(
"Metadata information for the call {}_{} could not be found",
pallet_name,
call_name
)
});
let fn_name = format_ident!("{}", variant_name.to_snake_case());
// Propagate the documentation just to `TransactionApi` methods, while
@@ -113,29 +97,19 @@ pub fn generate_calls(
// The call structure's documentation was stripped above.
let call_struct = quote! {
#struct_def
impl ::subxt::Call for #struct_name {
const PALLET: &'static str = #pallet_name;
const FUNCTION: &'static str = #call_name;
}
};
let client_fn = quote! {
#docs
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> Result<::subxt::SubmittableExtrinsic<'a, T, X, #struct_name, DispatchError, root_mod::Event>, ::subxt::BasicError> {
let runtime_call_hash = {
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
metadata.call_hash::<#struct_name>()?
};
if runtime_call_hash == [#(#call_hash,)*] {
let call = #struct_name { #( #call_args, )* };
Ok(::subxt::SubmittableExtrinsic::new(self.client, call))
} else {
Err(::subxt::MetadataError::IncompatibleMetadata.into())
}
) -> ::subxt::tx::StaticTxPayload<#struct_name> {
::subxt::tx::StaticTxPayload::new(
#pallet_name,
#call_name,
#struct_name { #( #call_args, )* },
[#(#call_hash,)*]
)
}
};
(call_struct, client_fn)
@@ -155,20 +129,9 @@ pub fn generate_calls(
#( #call_structs )*
pub struct TransactionApi<'a, T: ::subxt::Config, X> {
client: &'a ::subxt::Client<T>,
marker: ::core::marker::PhantomData<X>,
}
impl<'a, T, X> TransactionApi<'a, T, X>
where
T: ::subxt::Config,
X: ::subxt::extrinsic::ExtrinsicParams<T>,
{
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
}
pub struct TransactionApi;
impl TransactionApi {
#( #call_fns )*
}
}
+8 -19
View File
@@ -63,17 +63,12 @@ pub fn generate_constants(
quote! {
#( #[doc = #docs ] )*
pub fn #fn_name(&self) -> ::core::result::Result<#return_ty, ::subxt::BasicError> {
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
if metadata.constant_hash(#pallet_name, #constant_name)? == [#(#constant_hash,)*] {
let pallet = metadata.pallet(#pallet_name)?;
let constant = pallet.constant(#constant_name)?;
let value = ::subxt::codec::Decode::decode(&mut &constant.value[..])?;
Ok(value)
} else {
Err(::subxt::MetadataError::IncompatibleMetadata.into())
}
pub fn #fn_name(&self) -> ::subxt::constants::StaticConstantAddress<::subxt::metadata::DecodeStaticType<#return_ty>> {
::subxt::constants::StaticConstantAddress::new(
#pallet_name,
#constant_name,
[#(#constant_hash,)*]
)
}
}
});
@@ -82,15 +77,9 @@ pub fn generate_constants(
pub mod constants {
use super::#types_mod_ident;
pub struct ConstantsApi<'a, T: ::subxt::Config> {
client: &'a ::subxt::Client<T>,
}
impl<'a, T: ::subxt::Config> ConstantsApi<'a, T> {
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
Self { client }
}
pub struct ConstantsApi;
impl ConstantsApi {
#(#constant_fns)*
}
}
-189
View File
@@ -1,189 +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 frame_metadata::v14::RuntimeMetadataV14;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::abort_call_site;
use quote::quote;
use scale_info::{
form::PortableForm,
Field,
TypeDef,
TypeDefPrimitive,
};
/// Different substrate versions will have a different `DispatchError::Module`.
/// The following cases are ordered by versions.
enum ModuleErrorType {
/// Case 1: `DispatchError::Module { index: u8, error: u8 }`
///
/// This is the first supported `DispatchError::Module` format.
NamedField,
/// Case 2: `DispatchError::Module ( sp_runtime::ModuleError { index: u8, error: u8 } )`
///
/// Substrate introduced `sp_runtime::ModuleError`, while keeping the error `u8`.
LegacyError,
/// Case 3: `DispatchError::Module ( sp_runtime::ModuleError { index: u8, error: [u8; 4] } )`
///
/// The substrate error evolved into `[u8; 4]`.
ArrayError,
}
impl quote::ToTokens for ModuleErrorType {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let trait_fn_body = match self {
ModuleErrorType::NamedField => {
quote! {
if let &Self::Module { index, error } = self {
Some(::subxt::ModuleErrorData { pallet_index: index, error: [error, 0, 0, 0] })
} else {
None
}
}
}
ModuleErrorType::LegacyError => {
quote! {
if let Self::Module (module_error) = self {
Some(::subxt::ModuleErrorData { pallet_index: module_error.index, error: [module_error.error, 0, 0, 0] })
} else {
None
}
}
}
ModuleErrorType::ArrayError => {
quote! {
if let Self::Module (module_error) = self {
Some(::subxt::ModuleErrorData { pallet_index: module_error.index, error: module_error.error })
} else {
None
}
}
}
};
tokens.extend(trait_fn_body);
}
}
/// Determine the `ModuleError` type for the `ModuleErrorType::LegacyError` and
/// `ModuleErrorType::ErrorArray` cases.
fn module_error_type(
module_field: &Field<PortableForm>,
metadata: &RuntimeMetadataV14,
) -> ModuleErrorType {
// Fields are named.
if module_field.name().is_some() {
return ModuleErrorType::NamedField
}
// Get the `sp_runtime::ModuleError` structure.
let module_err = metadata
.types
.resolve(module_field.ty().id())
.unwrap_or_else(|| {
abort_call_site!("sp_runtime::ModuleError type expected in metadata")
});
let error_type_def = match module_err.type_def() {
TypeDef::Composite(composite) => composite,
_ => abort_call_site!("sp_runtime::ModuleError type should be a composite type"),
};
// Get the error field from the `sp_runtime::ModuleError` structure.
let error_field = error_type_def
.fields()
.iter()
.find(|field| field.name() == Some(&"error".to_string()))
.unwrap_or_else(|| {
abort_call_site!("sp_runtime::ModuleError expected to contain error field")
});
// Resolve the error type from the metadata.
let error_field_ty = metadata
.types
.resolve(error_field.ty().id())
.unwrap_or_else(|| {
abort_call_site!("sp_runtime::ModuleError::error type expected in metadata")
});
match error_field_ty.type_def() {
// Check for legacy error type.
TypeDef::Primitive(TypeDefPrimitive::U8) => ModuleErrorType::LegacyError,
TypeDef::Array(array) => {
// Check new error type of len 4 and type u8.
if array.len() != 4 {
abort_call_site!("sp_runtime::ModuleError::error array length is not 4");
}
let array_ty = metadata
.types
.resolve(array.type_param().id())
.unwrap_or_else(|| {
abort_call_site!(
"sp_runtime::ModuleError::error array type expected in metadata"
)
});
if let TypeDef::Primitive(TypeDefPrimitive::U8) = array_ty.type_def() {
ModuleErrorType::ArrayError
} else {
abort_call_site!(
"sp_runtime::ModuleError::error array type expected to be u8"
)
}
}
_ => {
abort_call_site!(
"sp_runtime::ModuleError::error array type or primitive expected"
)
}
}
}
/// The aim of this is to implement the `::subxt::HasModuleError` trait for
/// the generated `DispatchError`, so that we can obtain the module error details,
/// if applicable, from it.
pub fn generate_has_module_error_impl(
metadata: &RuntimeMetadataV14,
types_mod_ident: &syn::Ident,
) -> TokenStream2 {
let dispatch_error = metadata
.types
.types()
.iter()
.find(|&ty| ty.ty().path().segments() == ["sp_runtime", "DispatchError"])
.unwrap_or_else(|| {
abort_call_site!("sp_runtime::DispatchError type expected in metadata")
})
.ty()
.type_def();
// Get the `DispatchError::Module` variant (either struct or named fields).
let module_variant = match dispatch_error {
TypeDef::Variant(variant) => {
variant
.variants()
.iter()
.find(|variant| variant.name() == "Module")
.unwrap_or_else(|| {
abort_call_site!("DispatchError::Module variant expected in metadata")
})
}
_ => abort_call_site!("DispatchError expected to contain variant in metadata"),
};
let module_field = module_variant.fields().get(0).unwrap_or_else(|| {
abort_call_site!("DispatchError::Module expected to contain 1 or more fields")
});
let error_type = module_error_type(module_field, metadata);
quote! {
impl ::subxt::HasModuleError for #types_mod_ident::sp_runtime::DispatchError {
fn module_error_data(&self) -> Option<::subxt::ModuleErrorData> {
#error_type
}
}
}
}
+4 -3
View File
@@ -11,6 +11,7 @@ use scale_info::form::PortableForm;
/// Generate events from the provided pallet metadata.
///
/// The function creates a new module named `events` under the pallet's module.
///
/// ```ignore
/// pub mod PalletName {
/// pub mod events {
@@ -19,14 +20,14 @@ use scale_info::form::PortableForm;
/// }
/// ```
///
/// The function generates the events as rust structs that implement the `subxt::Event` trait
/// The function generates the events as rust structs that implement the `subxt::event::StaticEvent` trait
/// to uniquely identify the event's identity when creating the extrinsic.
///
/// ```ignore
/// pub struct EventName {
/// pub event_param: type,
/// }
/// impl ::subxt::Event for EventName {
/// impl ::subxt::events::StaticEvent for EventName {
/// ...
/// }
/// ```
@@ -62,7 +63,7 @@ pub fn generate_events(
quote! {
#struct_def
impl ::subxt::Event for #event_struct {
impl ::subxt::events::StaticEvent for #event_struct {
const PALLET: &'static str = #pallet_name;
const EVENT: &'static str = #event_name;
}
+37 -111
View File
@@ -6,7 +6,6 @@
mod calls;
mod constants;
mod errors;
mod events;
mod storage;
@@ -110,33 +109,33 @@ impl RuntimeGenerator {
let mut type_substitutes = [
(
"bitvec::order::Lsb0",
parse_quote!(::subxt::bitvec::order::Lsb0),
parse_quote!(::subxt::ext::bitvec::order::Lsb0),
),
(
"bitvec::order::Msb0",
parse_quote!(::subxt::bitvec::order::Msb0),
parse_quote!(::subxt::ext::bitvec::order::Msb0),
),
(
"sp_core::crypto::AccountId32",
parse_quote!(::subxt::sp_core::crypto::AccountId32),
parse_quote!(::subxt::ext::sp_core::crypto::AccountId32),
),
(
"primitive_types::H256",
parse_quote!(::subxt::sp_core::H256),
parse_quote!(::subxt::ext::sp_core::H256),
),
(
"sp_runtime::multiaddress::MultiAddress",
parse_quote!(::subxt::sp_runtime::MultiAddress),
parse_quote!(::subxt::ext::sp_runtime::MultiAddress),
),
(
"frame_support::traits::misc::WrapperKeepOpaque",
parse_quote!(::subxt::WrapperKeepOpaque),
parse_quote!(::subxt::utils::WrapperKeepOpaque),
),
// BTreeMap and BTreeSet impose an `Ord` constraint on their key types. This
// can cause an issue with generated code that doesn't impl `Ord` by default.
// Decoding them to Vec by default (KeyedVec is just an alias for Vec with
// suitable type params) avoids these issues.
("BTreeMap", parse_quote!(::subxt::KeyedVec)),
("BTreeMap", parse_quote!(::subxt::utils::KeyedVec)),
("BTreeSet", parse_quote!(::std::vec::Vec)),
]
.iter()
@@ -256,9 +255,6 @@ impl RuntimeGenerator {
})
.collect();
let has_module_error_impl =
errors::generate_has_module_error_impl(&self.metadata, types_mod_ident);
quote! {
#[allow(dead_code, unused_imports, non_camel_case_types)]
pub mod #mod_ident {
@@ -271,128 +267,58 @@ impl RuntimeGenerator {
#( #modules )*
#types_mod
/// The default error type returned when there is a runtime issue.
/// The default error type returned when there is a runtime issue,
/// exposed here for ease of use.
pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
// Impl HasModuleError on DispatchError so we can pluck out module error details.
#has_module_error_impl
pub struct RuntimeApi<T: ::subxt::Config, X> {
pub client: ::subxt::Client<T>,
marker: ::core::marker::PhantomData<X>,
pub fn constants() -> ConstantsApi {
ConstantsApi
}
impl<T: ::subxt::Config, X> Clone for RuntimeApi<T, X> {
fn clone(&self) -> Self {
Self { client: self.client.clone(), marker: ::core::marker::PhantomData }
}
pub fn storage() -> StorageApi {
StorageApi
}
impl<T, X> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X>
where
T: ::subxt::Config,
X: ::subxt::extrinsic::ExtrinsicParams<T>
{
fn from(client: ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
}
pub fn tx() -> TransactionApi {
TransactionApi
}
impl<'a, T, X> RuntimeApi<T, X>
where
T: ::subxt::Config,
X: ::subxt::extrinsic::ExtrinsicParams<T>,
{
pub fn validate_metadata(&'a self) -> Result<(), ::subxt::MetadataError> {
let runtime_metadata_hash = {
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
metadata.metadata_hash(&PALLETS)
};
if runtime_metadata_hash != [ #(#metadata_hash,)* ] {
Err(::subxt::MetadataError::IncompatibleMetadata)
} else {
Ok(())
}
}
pub fn constants(&'a self) -> ConstantsApi<'a, T> {
ConstantsApi { client: &self.client }
}
pub fn storage(&'a self) -> StorageApi<'a, T> {
StorageApi { client: &self.client }
}
pub fn tx(&'a self) -> TransactionApi<'a, T, X> {
TransactionApi { client: &self.client, marker: ::core::marker::PhantomData }
}
pub fn events(&'a self) -> EventsApi<'a, T> {
EventsApi { client: &self.client }
}
}
pub struct EventsApi<'a, T: ::subxt::Config> {
client: &'a ::subxt::Client<T>,
}
impl <'a, T: ::subxt::Config> EventsApi<'a, T> {
pub async fn at(&self, block_hash: T::Hash) -> Result<::subxt::events::Events<T, Event>, ::subxt::BasicError> {
::subxt::events::at::<T, Event>(self.client, block_hash).await
}
pub async fn subscribe(&self) -> Result<::subxt::events::EventSubscription<'a, ::subxt::events::EventSub<T::Header>, T, Event>, ::subxt::BasicError> {
::subxt::events::subscribe::<T, Event>(self.client).await
}
pub async fn subscribe_finalized(&self) -> Result<::subxt::events::EventSubscription<'a, ::subxt::events::FinalizedEventSub<'a, T::Header>, T, Event>, ::subxt::BasicError> {
::subxt::events::subscribe_finalized::<T, Event>(self.client).await
}
}
pub struct ConstantsApi<'a, T: ::subxt::Config> {
client: &'a ::subxt::Client<T>,
}
impl<'a, T: ::subxt::Config> ConstantsApi<'a, T> {
pub struct ConstantsApi;
impl ConstantsApi {
#(
pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi<'a, T> {
#pallets_with_constants::constants::ConstantsApi::new(self.client)
pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi {
#pallets_with_constants::constants::ConstantsApi
}
)*
}
pub struct StorageApi<'a, T: ::subxt::Config> {
client: &'a ::subxt::Client<T>,
}
impl<'a, T> StorageApi<'a, T>
where
T: ::subxt::Config,
{
pub struct StorageApi;
impl StorageApi {
#(
pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi<'a, T> {
#pallets_with_storage::storage::StorageApi::new(self.client)
pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi {
#pallets_with_storage::storage::StorageApi
}
)*
}
pub struct TransactionApi<'a, T: ::subxt::Config, X> {
client: &'a ::subxt::Client<T>,
marker: ::core::marker::PhantomData<X>,
}
impl<'a, T, X> TransactionApi<'a, T, X>
where
T: ::subxt::Config,
X: ::subxt::extrinsic::ExtrinsicParams<T>,
{
pub struct TransactionApi;
impl TransactionApi {
#(
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X> {
#pallets_with_calls::calls::TransactionApi::new(self.client)
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi {
#pallets_with_calls::calls::TransactionApi
}
)*
}
/// check whether the Client you are using is aligned with the statically generated codegen.
pub fn validate_codegen<T: ::subxt::Config, C: ::subxt::client::OfflineClientT<T>>(client: &C) -> Result<(), ::subxt::error::MetadataError> {
let runtime_metadata_hash = client.metadata().metadata_hash(&PALLETS);
if runtime_metadata_hash != [ #(#metadata_hash,)* ] {
Err(::subxt::error::MetadataError::IncompatibleMetadata)
} else {
Ok(())
}
}
}
}
}
+77 -211
View File
@@ -23,32 +23,8 @@ use scale_info::{
TypeDef,
};
/// Generate storage from the provided pallet's metadata.
///
/// The function creates a new module named `storage` under the pallet's module.
///
/// ```ignore
/// pub mod PalletName {
/// pub mod storage {
/// ...
/// }
/// }
/// ```
///
/// The function generates the storage as rust structs that implement the `subxt::StorageEntry`
/// trait to uniquely identify the storage's identity when creating the extrinsic.
///
/// ```ignore
/// pub struct StorageName {
/// pub storage_param: type,
/// }
/// impl ::subxt::StorageEntry for StorageName {
/// ...
/// }
/// ```
///
/// Storages are extracted from the API and wrapped into the generated `StorageApi` of
/// each module.
/// Generate functions which create storage addresses from the provided pallet's metadata.
/// These addresses can be used to access and iterate over storage values.
///
/// # Arguments
///
@@ -68,27 +44,19 @@ pub fn generate_storage(
return quote!()
};
let (storage_structs, storage_fns): (Vec<_>, Vec<_>) = storage
let storage_fns: Vec<_> = storage
.entries
.iter()
.map(|entry| generate_storage_entry_fns(metadata, type_gen, pallet, entry))
.unzip();
.collect();
quote! {
pub mod storage {
use super::#types_mod_ident;
#( #storage_structs )*
pub struct StorageApi<'a, T: ::subxt::Config> {
client: &'a ::subxt::Client<T>,
}
impl<'a, T: ::subxt::Config> StorageApi<'a, T> {
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
Self { client }
}
pub struct StorageApi;
impl StorageApi {
#( #storage_fns )*
}
}
@@ -100,16 +68,9 @@ fn generate_storage_entry_fns(
type_gen: &TypeGenerator,
pallet: &PalletMetadata<PortableForm>,
storage_entry: &StorageEntryMetadata<PortableForm>,
) -> (TokenStream2, TokenStream2) {
let entry_struct_ident = format_ident!("{}", storage_entry.name);
let (fields, entry_struct, constructor, key_impl, should_ref) = match storage_entry.ty
{
StorageEntryType::Plain(_) => {
let entry_struct = quote!( pub struct #entry_struct_ident; );
let constructor = quote!( #entry_struct_ident );
let key_impl = quote!(::subxt::StorageEntryKey::Plain);
(vec![], entry_struct, constructor, key_impl, false)
}
) -> TokenStream2 {
let (fields, key_impl) = match storage_entry.ty {
StorageEntryType::Plain(_) => (vec![], quote!(vec![])),
StorageEntryType::Map {
ref key,
ref hashers,
@@ -129,7 +90,7 @@ fn generate_storage_entry_fns(
StorageHasher::Identity => "Identity",
};
let hasher = format_ident!("{}", hasher);
quote!( ::subxt::StorageHasher::#hasher )
quote!( ::subxt::storage::address::StorageHasher::#hasher )
})
.collect::<Vec<_>>();
match key_ty.type_def() {
@@ -145,60 +106,28 @@ fn generate_storage_entry_fns(
})
.collect::<Vec<_>>();
let field_names = fields.iter().map(|(n, _)| n);
let field_types = fields.iter().map(|(_, t)| {
// If the field type is `::std::vec::Vec<T>` obtain the type parameter and
// surround with slice brackets. Otherwise, utilize the field_type as is.
match t.vec_type_param() {
Some(ty) => quote!([#ty]),
None => quote!(#t),
}
});
// There cannot be a reference without a parameter.
let should_ref = !fields.is_empty();
let (entry_struct, constructor) = if should_ref {
(
quote! {
pub struct #entry_struct_ident <'a>( #( pub &'a #field_types ),* );
},
quote!( #entry_struct_ident( #( #field_names ),* ) ),
)
} else {
(
quote!( pub struct #entry_struct_ident; ),
quote!( #entry_struct_ident ),
)
};
let key_impl = if hashers.len() == fields.len() {
// If the number of hashers matches the number of fields, we're dealing with
// something shaped like a StorageNMap, and each field should be hashed separately
// according to the corresponding hasher.
let keys = hashers
.into_iter()
.enumerate()
.map(|(field_idx, hasher)| {
let index = syn::Index::from(field_idx);
quote!( ::subxt::StorageMapKey::new(&self.#index, #hasher) )
.zip(&fields)
.map(|(hasher, (field_name, _))| {
quote!( ::subxt::storage::address::StorageMapKey::new(#field_name.borrow(), #hasher) )
});
quote! {
::subxt::StorageEntryKey::Map(
vec![ #( #keys ),* ]
)
vec![ #( #keys ),* ]
}
} else if hashers.len() == 1 {
// If there is one hasher, then however many fields we have, we want to hash a
// tuple of them using the one hasher we're told about. This corresponds to a
// StorageMap.
let hasher = hashers.get(0).expect("checked for 1 hasher");
let items = (0..fields.len()).map(|field_idx| {
let index = syn::Index::from(field_idx);
quote!( &self.#index )
});
let items =
fields.iter().map(|(field_name, _)| quote!( #field_name ));
quote! {
::subxt::StorageEntryKey::Map(
vec![ ::subxt::StorageMapKey::new(&(#( #items ),*), #hasher) ]
)
vec![ ::subxt::storage::address::StorageMapKey::new(&(#( #items.borrow() ),*), #hasher) ]
}
} else {
// If we hit this condition, we don't know how to handle the number of hashes vs fields
@@ -210,33 +139,18 @@ fn generate_storage_entry_fns(
)
};
(fields, entry_struct, constructor, key_impl, should_ref)
(fields, key_impl)
}
_ => {
let (lifetime_param, lifetime_ref) = (quote!(<'a>), quote!(&'a));
let ty_path = type_gen.resolve_type_path(key.id(), &[]);
let fields = vec![(format_ident!("_0"), ty_path.clone())];
// `ty_path` can be `std::vec::Vec<T>`. In such cases, the entry struct
// should contain a slice reference.
let ty_slice = match ty_path.vec_type_param() {
Some(ty) => quote!([#ty]),
None => quote!(#ty_path),
};
let entry_struct = quote! {
pub struct #entry_struct_ident #lifetime_param( pub #lifetime_ref #ty_slice );
};
let constructor = quote!( #entry_struct_ident(_0) );
let fields = vec![(format_ident!("_0"), ty_path)];
let hasher = hashers.get(0).unwrap_or_else(|| {
abort_call_site!("No hasher found for single key")
});
let key_impl = quote! {
::subxt::StorageEntryKey::Map(
vec![ ::subxt::StorageMapKey::new(&self.0, #hasher) ]
)
vec![ ::subxt::storage::address::StorageMapKey::new(_0.borrow(), #hasher) ]
};
(fields, entry_struct, constructor, key_impl, true)
(fields, key_impl)
}
}
}
@@ -255,133 +169,85 @@ fn generate_storage_entry_fns(
});
let fn_name = format_ident!("{}", storage_entry.name.to_snake_case());
let fn_name_iter = format_ident!("{}_iter", fn_name);
let storage_entry_ty = match storage_entry.ty {
StorageEntryType::Plain(ref ty) => ty,
StorageEntryType::Map { ref value, .. } => value,
};
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty.id(), &[]);
let (return_ty, fetch) = match storage_entry.modifier {
StorageEntryModifier::Default => {
(quote!( #storage_entry_value_ty ), quote!(fetch_or_default))
}
StorageEntryModifier::Optional => {
(
quote!( ::core::option::Option<#storage_entry_value_ty> ),
quote!(fetch),
)
}
};
let storage_entry_impl = quote! (
const PALLET: &'static str = #pallet_name;
const STORAGE: &'static str = #storage_name;
type Value = #storage_entry_value_ty;
fn key(&self) -> ::subxt::StorageEntryKey {
#key_impl
}
);
let anon_lifetime = match should_ref {
true => quote!(<'_>),
false => quote!(),
};
let storage_entry_type = quote! {
#entry_struct
impl ::subxt::StorageEntry for #entry_struct_ident #anon_lifetime {
#storage_entry_impl
}
};
let docs = &storage_entry.docs;
let docs_token = quote! { #( #[doc = #docs ] )* };
let lifetime_param = match should_ref {
true => quote!(<'a>),
false => quote!(),
let key_args = fields.iter().map(|(field_name, field_type)| {
// The field type is translated from `std::vec::Vec<T>` to `[T]`. We apply
// AsRef to all types, so this just makes it a little more ergonomic.
//
// TODO [jsdw]: Support mappings like `String -> str` too for better borrow
// ergonomics.
let field_ty = match field_type.vec_type_param() {
Some(ty) => quote!([#ty]),
_ => quote!(#field_type),
};
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> )
});
let is_map_type = matches!(storage_entry.ty, StorageEntryType::Map { .. });
// Is the entry iterable?
let is_iterable_type = if is_map_type {
quote!(::subxt::storage::address::Yes)
} else {
quote!(())
};
let client_iter_fn = if matches!(storage_entry.ty, StorageEntryType::Map { .. }) {
let has_default_value = match storage_entry.modifier {
StorageEntryModifier::Default => true,
StorageEntryModifier::Optional => false,
};
// Does the entry have a default value?
let is_defaultable_type = if has_default_value {
quote!(::subxt::storage::address::Yes)
} else {
quote!(())
};
// If the item is a map, we want a way to access the root entry to do things like iterate over it,
// so expose a function to create this entry, too:
let root_entry_fn = if is_map_type {
let fn_name_root = format_ident!("{}_root", fn_name);
quote! (
#docs_token
pub fn #fn_name_iter(
pub fn #fn_name_root(
&self,
block_hash: ::core::option::Option<T::Hash>,
) -> impl ::core::future::Future<
Output = ::core::result::Result<::subxt::KeyIter<'a, T, #entry_struct_ident #lifetime_param>, ::subxt::BasicError>
> + 'a {
// Instead of an async fn which borrows all of self,
// we make sure that the returned future only borrows
// client, which allows you to chain calls a little better.
let client = self.client;
async move {
let runtime_storage_hash = {
let locked_metadata = client.metadata();
let metadata = locked_metadata.read();
match metadata.storage_hash::<#entry_struct_ident>() {
Ok(hash) => hash,
Err(e) => return Err(e.into())
}
};
if runtime_storage_hash == [#(#storage_hash,)*] {
client.storage().iter(block_hash).await
} else {
Err(::subxt::MetadataError::IncompatibleMetadata.into())
}
}
) -> ::subxt::storage::address::StaticStorageAddress::<::subxt::metadata::DecodeStaticType<#storage_entry_value_ty>, (), #is_defaultable_type, #is_iterable_type> {
::subxt::storage::address::StaticStorageAddress::new(
#pallet_name,
#storage_name,
Vec::new(),
[#(#storage_hash,)*]
)
}
)
} else {
quote!()
};
let key_args_ref = match should_ref {
true => quote!(&'a),
false => quote!(),
};
let key_args = fields.iter().map(|(field_name, field_type)| {
// The field type is translated from `std::vec::Vec<T>` to `[T]`, if the
// interface should generate a reference. In such cases, the vector ultimately is
// a slice.
let field_ty = match field_type.vec_type_param() {
Some(ty) if should_ref => quote!([#ty]),
_ => quote!(#field_type),
};
quote!( #field_name: #key_args_ref #field_ty )
});
let client_fns = quote! {
quote! {
// Access a specific value from a storage entry
#docs_token
pub fn #fn_name(
&self,
#( #key_args, )*
block_hash: ::core::option::Option<T::Hash>,
) -> impl ::core::future::Future<
Output = ::core::result::Result<#return_ty, ::subxt::BasicError>
> + 'a {
// Instead of an async fn which borrows all of self,
// we make sure that the returned future only borrows
// client, which allows you to chain calls a little better.
let client = self.client;
async move {
let runtime_storage_hash = {
let locked_metadata = client.metadata();
let metadata = locked_metadata.read();
match metadata.storage_hash::<#entry_struct_ident>() {
Ok(hash) => hash,
Err(e) => return Err(e.into())
}
};
if runtime_storage_hash == [#(#storage_hash,)*] {
let entry = #constructor;
client.storage().#fetch(&entry, block_hash).await
} else {
Err(::subxt::MetadataError::IncompatibleMetadata.into())
}
}
) -> ::subxt::storage::address::StaticStorageAddress::<::subxt::metadata::DecodeStaticType<#storage_entry_value_ty>, ::subxt::storage::address::Yes, #is_defaultable_type, #is_iterable_type> {
::subxt::storage::address::StaticStorageAddress::new(
#pallet_name,
#storage_name,
#key_impl,
[#(#storage_hash,)*]
)
}
#client_iter_fn
};
(storage_entry_type, client_fns)
#root_entry_fn
}
}
+4 -4
View File
@@ -69,9 +69,9 @@ impl FromIterator<syn::Path> for Derives {
}
impl Derives {
/// Add `::subxt::codec::CompactAs` to the derives.
/// Add `::subxt::ext::codec::CompactAs` to the derives.
pub fn insert_codec_compact_as(&mut self) {
self.insert(parse_quote!(::subxt::codec::CompactAs));
self.insert(parse_quote!(::subxt::ext::codec::CompactAs));
}
pub fn append(&mut self, derives: impl Iterator<Item = syn::Path>) {
@@ -88,8 +88,8 @@ impl Derives {
impl Default for Derives {
fn default() -> Self {
let mut derives = HashSet::new();
derives.insert(syn::parse_quote!(::subxt::codec::Encode));
derives.insert(syn::parse_quote!(::subxt::codec::Decode));
derives.insert(syn::parse_quote!(::subxt::ext::codec::Encode));
derives.insert(syn::parse_quote!(::subxt::ext::codec::Decode));
derives.insert(syn::parse_quote!(Debug));
Self { derives }
}
+42 -42
View File
@@ -53,7 +53,7 @@ fn generate_struct_with_primitives() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub a: ::core::primitive::bool,
pub b: ::core::primitive::u32,
@@ -99,12 +99,12 @@ fn generate_struct_with_a_struct_field() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Child {
pub a: ::core::primitive::i32,
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Parent {
pub a: ::core::primitive::bool,
pub b: root::subxt_codegen::types::tests::Child,
@@ -144,10 +144,10 @@ fn generate_tuple_struct() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Child(pub ::core::primitive::i32,);
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Parent(pub ::core::primitive::bool, pub root::subxt_codegen::types::tests::Child,);
}
}
@@ -226,34 +226,34 @@ fn derive_compact_as_for_uint_wrapper_structs() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Su128 { pub a: ::core::primitive::u128, }
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Su16 { pub a: ::core::primitive::u16, }
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Su32 { pub a: ::core::primitive::u32, }
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Su64 { pub a: ::core::primitive::u64, }
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Su8 { pub a: ::core::primitive::u8, }
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct TSu128(pub ::core::primitive::u128,);
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct TSu16(pub ::core::primitive::u16,);
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct TSu32(pub ::core::primitive::u32,);
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct TSu64(pub ::core::primitive::u64,);
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct TSu8(pub ::core::primitive::u8,);
}
}
@@ -289,7 +289,7 @@ fn generate_enum() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub enum E {
# [codec (index = 0)]
A,
@@ -347,7 +347,7 @@ fn compact_fields() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub enum E {
# [codec (index = 0)]
A {
@@ -358,12 +358,12 @@ fn compact_fields() {
B( #[codec(compact)] ::core::primitive::u32,),
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
#[codec(compact)] pub a: ::core::primitive::u32,
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct TupleStruct(#[codec(compact)] pub ::core::primitive::u32,);
}
}
@@ -397,7 +397,7 @@ fn generate_array_field() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub a: [::core::primitive::u8; 32usize],
}
@@ -434,7 +434,7 @@ fn option_fields() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub a: ::core::option::Option<::core::primitive::bool>,
pub b: ::core::option::Option<::core::primitive::u32>,
@@ -474,7 +474,7 @@ fn box_fields_struct() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub a: ::std::boxed::Box<::core::primitive::bool>,
pub b: ::std::boxed::Box<::core::primitive::u32>,
@@ -514,7 +514,7 @@ fn box_fields_enum() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub enum E {
# [codec (index = 0)]
A(::std::boxed::Box<::core::primitive::bool>,),
@@ -554,7 +554,7 @@ fn range_fields() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub a: ::core::ops::Range<::core::primitive::u32>,
pub b: ::core::ops::RangeInclusive<::core::primitive::u32>,
@@ -598,12 +598,12 @@ fn generics() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Bar {
pub b: root::subxt_codegen::types::tests::Foo<::core::primitive::u32>,
pub c: root::subxt_codegen::types::tests::Foo<::core::primitive::u8>,
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Foo<_0> {
pub a: _0,
}
@@ -646,12 +646,12 @@ fn generics_nested() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Bar<_0> {
pub b: root::subxt_codegen::types::tests::Foo<_0, ::core::primitive::u32>,
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Foo<_0, _1> {
pub a: _0,
pub b: ::core::option::Option<(_0, _1,)>,
@@ -697,10 +697,10 @@ fn generate_bitvec() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub lsb: ::subxt::bitvec::vec::BitVec<::core::primitive::u8, root::bitvec::order::Lsb0>,
pub msb: ::subxt::bitvec::vec::BitVec<::core::primitive::u16, root::bitvec::order::Msb0>,
pub lsb: ::subxt::ext::bitvec::vec::BitVec<::core::primitive::u8, root::bitvec::order::Lsb0>,
pub msb: ::subxt::ext::bitvec::vec::BitVec<::core::primitive::u16, root::bitvec::order::Msb0>,
}
}
}
@@ -750,12 +750,12 @@ fn generics_with_alias_adds_phantom_data_marker() {
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::codec::CompactAs, ::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::CompactAs, ::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct NamedFields<_0> {
pub b: ::core::primitive::u32,
#[codec(skip)] pub __subxt_unused_type_params: ::core::marker::PhantomData<_0>
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct UnnamedFields<_0, _1> (
pub (::core::primitive::u32, ::core::primitive::u32,),
#[codec(skip)] pub ::core::marker::PhantomData<(_0, _1)>
@@ -818,20 +818,20 @@ fn modules() {
pub mod b {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Bar {
pub a: root::subxt_codegen::types::tests::m::a::Foo,
}
}
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Foo;
}
pub mod c {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct Foo {
pub a: root::subxt_codegen::types::tests::m::a::b::Bar,
}
@@ -868,7 +868,7 @@ fn dont_force_struct_names_camel_case() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct AB;
}
}
@@ -905,10 +905,10 @@ fn apply_user_defined_derives_for_all_types() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Clone, Debug, Eq)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Clone, Debug, Eq)]
pub struct A(pub root :: subxt_codegen :: types :: tests :: B,);
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Clone, Debug, Eq)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Clone, Debug, Eq)]
pub struct B;
}
}
@@ -964,13 +964,13 @@ fn apply_user_defined_derives_for_specific_types() {
pub mod tests {
use super::root;
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug, Eq)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug, Eq)]
pub struct A(pub root :: subxt_codegen :: types :: tests :: B,);
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug, Eq, Hash)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug, Eq, Hash)]
pub struct B(pub root :: subxt_codegen :: types :: tests :: C,);
#[derive(::subxt::codec::Decode, ::subxt::codec::Encode, Debug, Eq, Ord, PartialOrd)]
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug, Eq, Ord, PartialOrd)]
pub struct C;
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ impl TypePathType {
let bit_order_type = &self.params[0];
let bit_store_type = &self.params[1];
let type_path = parse_quote! { ::subxt::bitvec::vec::BitVec<#bit_store_type, #bit_order_type> };
let type_path = parse_quote! { ::subxt::ext::bitvec::vec::BitVec<#bit_store_type, #bit_order_type> };
syn::Type::Path(type_path)
}
+1
View File
@@ -19,3 +19,4 @@ futures = "0.3.13"
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
hex = "0.4.3"
tracing-subscriber = "0.3.11"
+15 -19
View File
@@ -2,20 +2,19 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -26,23 +25,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api = ClientBuilder::new()
.build()
.await?
.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();
// Obtain an extrinsic, calling the "transfer" function in
// the "balances" pallet.
let extrinsic = api.tx().balances().transfer(dest, 123_456_789_012_345)?;
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Sign and submit the extrinsic, returning its hash.
let tx_hash = extrinsic.sign_and_submit_default(&signer).await?;
// Create a transaction to submit:
let tx = polkadot::tx()
.balances()
.transfer(dest, 123_456_789_012_345);
println!("Balance transfer extrinsic submitted: {}", tx_hash);
// Submit the transaction with default params:
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Balance transfer extrinsic submitted: {}", hash);
Ok(())
}
@@ -2,25 +2,24 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{
extrinsic::{
tx::{
Era,
PairSigner,
PlainTip,
PolkadotExtrinsicParamsBuilder as Params,
},
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder as Params,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -33,23 +32,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a transaction to submit:
let tx = polkadot::tx()
.balances()
.transfer(dest, 123_456_789_012_345);
// Configure the transaction tip and era:
let tx_params = Params::new()
.tip(PlainTip::new(20_000_000_000))
.era(Era::Immortal, *api.client.genesis());
.era(Era::Immortal, api.genesis_hash());
// Send the transaction:
let hash = api
.tx()
.balances()
.transfer(dest, 123_456_789_012_345)?
.sign_and_submit(&signer, tx_params)
.await?;
// submit the transaction:
let hash = api.tx().sign_and_submit(&tx, &signer, tx_params).await?;
println!("Balance transfer extrinsic submitted: {}", hash);
@@ -2,12 +2,19 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use futures::join;
use sp_keyring::AccountKeyring;
use subxt::{
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -15,17 +22,18 @@ pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let addr = AccountKeyring::Bob.to_account_id();
// Construct storage addresses to access:
let staking_bonded = polkadot::storage().staking().bonded(&addr);
let staking_ledger = polkadot::storage().staking().ledger(&addr);
// For storage requests, we can join futures together to
// await multiple futures concurrently:
let a_fut = api.storage().staking().bonded(&addr, None);
let b_fut = api.storage().staking().ledger(&addr, None);
let a_fut = api.storage().fetch(&staking_bonded, None);
let b_fut = api.storage().fetch(&staking_ledger, None);
let (a, b) = join!(a_fut, b_fut);
println!("{a:?}, {b:?}");
+31 -30
View File
@@ -2,21 +2,20 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
//! This example should compile but should aos fail to work, since we've modified the
//! config to not align with a Polkadot node.
use sp_keyring::AccountKeyring;
use subxt::{
ClientBuilder,
Config,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
config::{
Config,
SubstrateConfig,
},
tx::{
PairSigner,
SubstrateExtrinsicParams,
},
OnlineClient,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -33,32 +32,34 @@ impl Config for MyConfig {
// polkadot runtime used, so some operations will fail. Normally when using a custom `Config`
// impl types MUST match exactly those used in the actual runtime.
type Index = u64;
type BlockNumber = <DefaultConfig as Config>::BlockNumber;
type Hash = <DefaultConfig as Config>::Hash;
type Hashing = <DefaultConfig as Config>::Hashing;
type AccountId = <DefaultConfig as Config>::AccountId;
type Address = <DefaultConfig as Config>::Address;
type Header = <DefaultConfig as Config>::Header;
type Signature = <DefaultConfig as Config>::Signature;
type Extrinsic = <DefaultConfig as Config>::Extrinsic;
type BlockNumber = <SubstrateConfig as Config>::BlockNumber;
type Hash = <SubstrateConfig as Config>::Hash;
type Hashing = <SubstrateConfig as Config>::Hashing;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <SubstrateConfig as Config>::Address;
type Header = <SubstrateConfig as Config>::Header;
type Signature = <SubstrateConfig as Config>::Signature;
type Extrinsic = <SubstrateConfig as Config>::Extrinsic;
// ExtrinsicParams makes use of the index type, so we need to adjust it
// too to align with our modified index type, above:
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<MyConfig, PolkadotExtrinsicParams<MyConfig>>>();
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let hash = api
.tx()
// Create a client to use:
let api = OnlineClient::<MyConfig>::new().await?;
// Create a transaction to submit:
let tx = polkadot::tx()
.balances()
.transfer(dest, 10_000)?
.sign_and_submit_default(&signer)
.await?;
.transfer(dest, 123_456_789_012_345);
// submit the transaction with default params:
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Balance transfer extrinsic submitted: {}", hash);
+1 -2
View File
@@ -2,8 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
#![allow(clippy::redundant_clone)]
#[subxt::subxt(
+82
View File
@@ -0,0 +1,82 @@
// 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.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
// This example showcases working with dynamic values rather than those that are generated via the subxt proc macro.
use sp_keyring::AccountKeyring;
use subxt::{
dynamic::Value,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// 1. Dynamic Balance Transfer (the dynamic equivalent to the balance_transfer example).
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id();
// Create a transaction to submit:
let tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
// A value representing a MultiAddress<AccountId32, _>. We want the "Id" variant, and that
// will ultimately contain the bytes for our destination address (there is a new type wrapping
// the address, but our encoding will happily ignore such things and do it's best to line up what
// we provide with what it needs).
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
// A value representing the amount we'd like to transfer.
Value::u128(123_456_789_012_345),
],
);
// submit the transaction with default params:
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Balance transfer extrinsic submitted: {}", hash);
// 2. Dynamic constant access (the dynamic equivalent to the fetch_constants example).
let constant_address = subxt::dynamic::constant("Balances", "ExistentialDeposit");
let existential_deposit = api.constants().at(&constant_address)?;
println!("Existential Deposit: {}", existential_deposit);
// 3. Dynamic storage access
let storage_address = subxt::dynamic::storage(
"System",
"Account",
vec![
// Something that encodes to an AccountId32 is what we need for the map key here:
Value::from_bytes(&dest),
],
);
let account = api
.storage()
.fetch_or_default(&storage_address, None)
.await?;
println!("Bob's account details: {account}");
// 4. Dynamic storage iteration (the dynamic equivalent to the fetch_all_accounts example).
let storage_address = subxt::dynamic::storage_root("System", "Account");
let mut iter = api.storage().iter(storage_address, 10, None).await?;
while let Some((key, account)) = iter.next().await? {
println!("{}: {}", hex::encode(key), account);
}
Ok(())
}
+8 -10
View File
@@ -2,18 +2,17 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -23,12 +22,11 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let mut iter = api.storage().system().account_iter(None).await?;
let address = polkadot::storage().system().account_root();
let mut iter = api.storage().iter(address, 10, None).await?;
while let Some((key, account)) = iter.next().await? {
println!("{}: {}", hex::encode(key), account.data.free);
+11 -20
View File
@@ -2,18 +2,17 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
// Generate the API from a static metadata path.
@@ -24,22 +23,14 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Upon connecting to the target polkadot node, the node's metadata is downloaded (referred to
// as the runtime metadata).
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Constants are queried from the node's runtime metadata.
// Query the `ExistentialDeposit` constant from the `Balances` pallet.
let existential_deposit = api
// This is the constants query.
.constants()
// Constant from the `Balances` pallet.
.balances()
// Constant name.
.existential_deposit()?;
// Build a constant address to query:
let address = polkadot::constants().balances().existential_deposit();
// Look it up:
let existential_deposit = api.constants().at(&address)?;
println!("Existential Deposit: {}", existential_deposit);
+18 -21
View File
@@ -2,24 +2,25 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{
sp_core::{
sr25519,
Pair,
ext::{
sp_core::{
sr25519,
Pair,
},
sp_runtime::AccountId32,
},
sp_runtime::AccountId32,
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -29,12 +30,11 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let era = api.storage().staking().active_era(None).await?.unwrap();
let active_era_addr = polkadot::storage().staking().active_era();
let era = api.storage().fetch(&active_era_addr, None).await?.unwrap();
println!(
"Staking active era: index: {:?}, start: {:?}",
era.index, era.start
@@ -51,19 +51,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" Alice//stash account id: {:?}", alice_stash_id);
// Map from all locked "stash" accounts to the controller account.
let controller_acc_addr = polkadot::storage().staking().bonded(&alice_stash_id);
let controller_acc = api
.storage()
.staking()
.bonded(&alice_stash_id, None)
.fetch(&controller_acc_addr, None)
.await?
.unwrap();
println!(" account controlled by: {:?}", controller_acc);
let era_result = api
.storage()
.staking()
.eras_reward_points(&era.index, None)
.await?;
let era_reward_addr = polkadot::storage().staking().eras_reward_points(&era.index);
let era_result = api.storage().fetch(&era_reward_addr, None).await?;
println!("Era reward points: {:?}", era_result);
Ok(())
+12 -16
View File
@@ -2,18 +2,17 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -23,18 +22,15 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Full metadata validation is not enabled by default; instead, the individual calls,
// storage requests and constant accesses are runtime type checked against the node
// metadata to ensure that they are compatible with the generated code.
//
// To make sure that all of our statically generated pallets are compatible with the
// runtime node, we can run this check:
api.validate_metadata()?;
// Each individual request will be validated against the static code that was
// used to construct it, if possible. We can also validate the entirety of the
// statically generated code against some client at a given point in time using
// this check. If it fails, then there is some breaking change between the metadata
// used to generate this static code, and the runtime metadata from a node that the
// client is using.
polkadot::validate_codegen(&api)?;
Ok(())
}
+6 -18
View File
@@ -2,40 +2,28 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.set_url("wss://rpc.polkadot.io:443")
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let block_number = 1u32;
let block_hash = api
.client
.rpc()
.block_hash(Some(block_number.into()))
.await?;
let block_hash = api.rpc().block_hash(Some(block_number.into())).await?;
if let Some(hash) = block_hash {
println!("Block hash for block number {block_number}: {hash}");
+9 -16
View File
@@ -2,41 +2,34 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.13/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use subxt::{
rpc::Subscription,
sp_runtime::{
ext::sp_runtime::{
generic::Header,
traits::BlakeTwo256,
},
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
rpc::Subscription,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.set_url("wss://rpc.polkadot.io:443")
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let api =
OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
// For non-finalised blocks use `.subscribe_blocks()`
let mut blocks: Subscription<Header<u32, BlakeTwo256>> =
api.client.rpc().subscribe_finalized_blocks().await?;
api.rpc().subscribe_finalized_blocks().await?;
while let Some(Ok(block)) = blocks.next().await {
println!(
+107
View File
@@ -0,0 +1,107 @@
// 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.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use codec::Decode;
use subxt::{
storage::address::{
StorageHasher,
StorageMapKey,
},
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Example 1. Iterate over (keys, value) using the storage client.
// This is the standard and most ergonomic approach.
{
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
let mut iter = api.storage().iter(key_addr, 10, None).await?;
println!("\nExample 1. Obtained keys:");
while let Some((key, value)) = iter.next().await? {
println!("Key: 0x{}", hex::encode(&key));
println!(" Value: {}", value);
}
}
// Example 2. Iterate over fetched keys manually. Here, you forgo any static type
// safety and work directly with the bytes on either side.
{
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
// Fetch at most 10 keys from below the prefix XcmPallet' VersionNotifiers.
let keys = api
.storage()
.fetch_keys(&key_addr.to_root_bytes(), 10, None, None)
.await?;
println!("Example 2. Obtained keys:");
for key in keys.iter() {
println!("Key: 0x{}", hex::encode(&key));
if let Some(storage_data) = api.storage().fetch_raw(&key.0, None).await? {
// We know the return value to be `QueryId` (`u64`) from inspecting either:
// - polkadot code
// - polkadot.rs generated file under `version_notifiers()` fn
// - metadata in json format
let value = u64::decode(&mut &*storage_data)?;
println!(" Value: {}", value);
}
}
}
// Example 3. Custom iteration over double maps. Here, we manually append one lookup
// key to the root and just iterate over the values underneath that.
{
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
// Obtain the root bytes (`twox_128("XcmPallet") ++ twox_128("VersionNotifiers")`).
let mut query_key = key_addr.to_root_bytes();
// We know that the first key is a u32 (the `XcmVersion`) and is hashed by twox64_concat.
// We can build a `StorageMapKey` that replicates that, and append those bytes to the above.
StorageMapKey::new(&2u32, StorageHasher::Twox64Concat).to_bytes(&mut query_key);
// The final query key is essentially the result of:
// `twox_128("XcmPallet") ++ twox_128("VersionNotifiers") ++ twox_64(2u32) ++ 2u32`
println!("\nExample 3\nQuery key: 0x{}", hex::encode(&query_key));
let keys = api.storage().fetch_keys(&query_key, 10, None, None).await?;
println!("Obtained keys:");
for key in keys.iter() {
println!("Key: 0x{}", hex::encode(&key));
if let Some(storage_data) = api.storage().fetch_raw(&key.0, None).await? {
// We know the return value to be `QueryId` (`u64`) from inspecting either:
// - polkadot code
// - polkadot.rs generated file under `version_notifiers()` fn
// - metadata in json format
let value = u64::decode(&mut &storage_data[..])?;
println!(" Value: {}", value);
}
}
}
Ok(())
}
-153
View File
@@ -1,153 +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.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use codec::Decode;
use subxt::{
rpc::Rpc,
storage::{
StorageClient,
StorageKeyPrefix,
},
ClientBuilder,
DefaultConfig,
PolkadotExtrinsicParams,
StorageEntryKey,
StorageMapKey,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Obtain the storage client wrapper from the API.
let storage: StorageClient<_> = api.client.storage();
// The VersionNotifiers type of the XcmPallet is defined as:
//
// ```
// All locations that we have requested version notifications from.
// #[pallet::storage]
// pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
// _,
// Twox64Concat,
// XcmVersion,
// Blake2_128Concat,
// VersionedMultiLocation,
// QueryId,
// OptionQuery,
// >;
// ```
// Example 1. Iterate over fetched keys manually.
{
// Fetch at most 10 keys from below the prefix XcmPallet' VersionNotifiers.
let keys = storage
.fetch_keys::<polkadot::xcm_pallet::storage::VersionNotifiers>(10, None, None)
.await?;
println!("Example 1. Obtained keys:");
for key in keys.iter() {
println!("Key: 0x{}", hex::encode(&key));
if let Some(storage_data) = storage.fetch_raw(key.clone(), None).await? {
// We know the return value to be `QueryId` (`u64`) from inspecting either:
// - polkadot code
// - polkadot.rs generated file under `version_notifiers()` fn
// - metadata in json format
let value = u64::decode(&mut &storage_data.0[..])?;
println!(" Value: {}", value);
}
}
}
// Example 2. Iterate over (keys, value) using the storage client.
{
let mut iter = storage
.iter::<polkadot::xcm_pallet::storage::VersionNotifiers>(None)
.await?;
println!("\nExample 2. Obtained keys:");
while let Some((key, value)) = iter.next().await? {
println!("Key: 0x{}", hex::encode(&key));
println!(" Value: {}", value);
}
}
// Example 3. Iterate over (keys, value) using the polkadot API.
{
let mut iter = api
.storage()
.xcm_pallet()
.version_notifiers_iter(None)
.await?;
println!("\nExample 3. Obtained keys:");
while let Some((key, value)) = iter.next().await? {
println!("Key: 0x{}", hex::encode(&key));
println!(" Value: {}", value);
}
}
// Example 4. Custom iteration over double maps.
{
// Obtain the inner RPC from the API.
let rpc: &Rpc<_> = api.client.rpc();
// Obtain the prefixed `twox_128("XcmPallet") ++ twox_128("VersionNotifiers")`
let prefix =
StorageKeyPrefix::new::<polkadot::xcm_pallet::storage::VersionNotifiers>();
// From the VersionNotifiers definition above, the first key is represented by
// ```
// Twox64Concat,
// XcmVersion,
// ```
// while `XcmVersion` is `u32`.
// Pass `2` as `XcmVersion` and concatenate the key to the prefix.
let entry_key = StorageEntryKey::Map(vec![StorageMapKey::new(
&2u32,
::subxt::StorageHasher::Twox64Concat,
)]);
// The final query key is:
// `twox_128("XcmPallet") ++ twox_128("VersionNotifiers") ++ twox_64(2u32) ++ 2u32`
let query_key = entry_key.final_key(prefix);
println!("\nExample 4\nQuery key: 0x{}", hex::encode(&query_key));
let keys = rpc
.storage_keys_paged(Some(query_key), 10, None, None)
.await?;
println!("Obtained keys:");
for key in keys.iter() {
println!("Key: 0x{}", hex::encode(&key));
if let Some(storage_data) = storage.fetch_raw(key.clone(), None).await? {
// We know the return value to be `QueryId` (`u64`) from inspecting either:
// - polkadot code
// - polkadot.rs generated file under `version_notifiers()` fn
// - metadata in json format
let value = u64::decode(&mut &storage_data.0[..])?;
println!(" Value: {}", value);
}
}
}
Ok(())
}
+18 -28
View File
@@ -2,21 +2,20 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use futures::StreamExt;
use sp_keyring::AccountKeyring;
use subxt::{
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -40,16 +39,13 @@ async fn simple_transfer() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let balance_transfer = api
.tx()
.balances()
.transfer(dest, 10_000)?
.sign_and_submit_then_watch_default(&signer)
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -72,16 +68,13 @@ async fn simple_transfer_separate_events() -> Result<(), Box<dyn std::error::Err
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let balance_transfer = api
.tx()
.balances()
.transfer(dest, 10_000)?
.sign_and_submit_then_watch_default(&signer)
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?
.wait_for_finalized()
.await?;
@@ -123,21 +116,18 @@ async fn handle_transfer_events() -> Result<(), Box<dyn std::error::Error>> {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let mut balance_transfer_progress = api
.tx()
.balances()
.transfer(dest, 10_000)?
.sign_and_submit_then_watch_default(&signer)
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?;
while let Some(ev) = balance_transfer_progress.next().await {
let ev = ev?;
use subxt::TransactionStatus::*;
use subxt::tx::TxStatus::*;
// Made it into a block, but not finalized.
if let InBlock(details) = ev {
+30 -47
View File
@@ -2,11 +2,11 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
@@ -14,10 +14,9 @@ use futures::StreamExt;
use sp_keyring::AccountKeyring;
use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -29,41 +28,33 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to any events that occur:
let mut event_sub = api.events().subscribe().await?;
// While this subscription is active, balance transfers are made somewhere:
tokio::task::spawn(async {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api =
ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<
DefaultConfig,
PolkadotExtrinsicParams<DefaultConfig>,
>>();
tokio::task::spawn({
let api = api.clone();
async move {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let mut transfer_amount = 1_000_000_000;
let mut transfer_amount = 1_000_000_000;
// Make small balance transfers from Alice to Bob in a loop:
loop {
let transfer_tx = polkadot::tx().balances().transfer(
AccountKeyring::Bob.to_account_id().into(),
transfer_amount,
);
api.tx()
.sign_and_submit_default(&transfer_tx, &signer)
.await
.unwrap();
// Make small balance transfers from Alice to Bob in a loop:
loop {
api.tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount)
.expect("compatible transfer call on runtime node")
.sign_and_submit_default(&signer)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(10)).await;
transfer_amount += 100_000_000;
tokio::time::sleep(Duration::from_secs(10)).await;
transfer_amount += 100_000_000;
}
}
});
@@ -72,29 +63,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let events = events?;
let block_hash = events.block_hash();
// We can iterate, statically decoding all events if we want:
println!("All events in block {block_hash:?}:");
println!(" Static event details:");
for event in events.iter() {
let event = event?;
println!(" {event:?}");
}
// Or we can dynamically decode events:
// We can dynamically decode events:
println!(" Dynamic event details: {block_hash:?}:");
for event in events.iter_raw() {
for event in events.iter() {
let event = event?;
let is_balance_transfer = event
.as_event::<polkadot::balances::events::Transfer>()?
.is_some();
let pallet = event.pallet;
let variant = event.variant;
let pallet = event.pallet_name();
let variant = event.variant_name();
println!(
" {pallet}::{variant} (is balance transfer? {is_balance_transfer})"
);
}
// Or we can dynamically find the first transfer event, ignoring any others:
// Or we can find the first transfer event, ignoring any others:
let transfer_event =
events.find_first::<polkadot::balances::events::Transfer>()?;
+27 -33
View File
@@ -2,11 +2,11 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
@@ -14,10 +14,9 @@ use futures::StreamExt;
use sp_keyring::AccountKeyring;
use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -29,11 +28,8 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Subscribe to any events that occur:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to just balance transfer events, making use of `filter_events`
// to select a single event type (note the 1-tuple) to filter out and return.
@@ -43,29 +39,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.await?
.filter_events::<(polkadot::balances::events::Transfer,)>();
// While this subscription is active, we imagine some balance transfers are made somewhere else:
tokio::task::spawn(async {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api =
ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<
DefaultConfig,
PolkadotExtrinsicParams<DefaultConfig>,
>>();
// While this subscription is active, balance transfers are made somewhere:
tokio::task::spawn({
let api = api.clone();
async move {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let mut transfer_amount = 1_000_000_000;
// Make small balance transfers from Alice to Bob in a loop:
loop {
api.tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), 1_000_000_000)
.expect("compatible transfer call on runtime node")
.sign_and_submit_default(&signer)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(10)).await;
// Make small balance transfers from Alice to Bob in a loop:
loop {
let transfer_tx = polkadot::tx().balances().transfer(
AccountKeyring::Bob.to_account_id().into(),
transfer_amount,
);
api.tx()
.sign_and_submit_default(&transfer_tx, &signer)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(10)).await;
transfer_amount += 100_000_000;
}
}
});
+10 -39
View File
@@ -2,67 +2,38 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Start a new tokio task to perform the runtime updates while
// utilizing the API for other use cases.
let update_client = api.client.updates();
let update_client = api.subscribe_to_updates();
tokio::spawn(async move {
let result = update_client.perform_runtime_updates().await;
println!("Runtime update failed with result={:?}", result);
});
// Make multiple transfers to simulate a long running `subxt::Client` use-case.
//
// Meanwhile, the tokio task above will perform any necessary updates to keep in sync
// with the node we've connected to. Transactions submitted in the vicinity of a runtime
// update may still fail, however, owing to a race between the update happening and
// subxt synchronising its internal state with it.
let signer = PairSigner::new(AccountKeyring::Alice.pair());
// Make small balance transfers from Alice to Bob:
for _ in 0..10 {
let hash = api
.tx()
.balances()
.transfer(
AccountKeyring::Bob.to_account_id().into(),
123_456_789_012_345,
)
.unwrap()
.sign_and_submit_default(&signer)
.await
.unwrap();
println!("Balance transfer extrinsic submitted: {}", hash);
tokio::time::sleep(Duration::from_secs(30)).await;
}
// If this client is kept in use a while, it'll update its metadata and such
// as needed when the node it's pointed at updates.
tokio::time::sleep(Duration::from_secs(10_000)).await;
Ok(())
}
+27 -33
View File
@@ -2,11 +2,11 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-4542a603cc-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.18/polkadot" --output /usr/local/bin/polkadot --location
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.25/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
@@ -14,10 +14,9 @@ use futures::StreamExt;
use sp_keyring::AccountKeyring;
use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
@@ -29,11 +28,8 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Subscribe to any events that occur:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to several balance related events. If we ask for more than one event,
// we'll be given a correpsonding tuple of `Option`'s, with exactly one
@@ -44,29 +40,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
polkadot::balances::events::Deposit,
)>();
// While this subscription is active, we imagine some balance transfers are made somewhere else:
tokio::task::spawn(async {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api =
ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<
DefaultConfig,
PolkadotExtrinsicParams<DefaultConfig>,
>>();
// While this subscription is active, balance transfers are made somewhere:
tokio::task::spawn({
let api = api.clone();
async move {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let mut transfer_amount = 1_000_000_000;
// Make small balance transfers from Alice to Bob in a loop:
loop {
api.tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), 1_000_000_000)
.expect("compatible transfer call on runtime node")
.sign_and_submit_default(&signer)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(10)).await;
// Make small balance transfers from Alice to Bob in a loop:
loop {
let transfer_tx = polkadot::tx().balances().transfer(
AccountKeyring::Bob.to_account_id().into(),
transfer_amount,
);
api.tx()
.sign_and_submit_default(&transfer_tx, &signer)
.await
.unwrap();
tokio::time::sleep(Duration::from_secs(10)).await;
transfer_amount += 100_000_000;
}
}
});
-214
View File
@@ -1,214 +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 crate::{
test_node_process,
test_node_process_with,
utils::{
node_runtime::system,
pair_signer,
test_context,
},
};
use sp_core::{
sr25519::Pair,
storage::{
well_known_keys,
StorageKey,
},
Pair as _,
};
use sp_keyring::AccountKeyring;
use sp_runtime::DispatchOutcome;
use subxt::Error;
#[tokio::test]
async fn insert_key() {
let test_node_process = test_node_process_with(AccountKeyring::Bob).await;
let client = test_node_process.client();
let public = AccountKeyring::Alice.public().as_array_ref().to_vec();
client
.rpc()
.insert_key(
"aura".to_string(),
"//Alice".to_string(),
public.clone().into(),
)
.await
.unwrap();
assert!(client
.rpc()
.has_key(public.clone().into(), "aura".to_string())
.await
.unwrap());
}
#[tokio::test]
async fn fetch_block_hash() {
let node_process = test_node_process().await;
node_process.client().rpc().block_hash(None).await.unwrap();
}
#[tokio::test]
async fn fetch_block() {
let node_process = test_node_process().await;
let client = node_process.client();
let block_hash = client.rpc().block_hash(None).await.unwrap();
client.rpc().block(block_hash).await.unwrap();
}
#[tokio::test]
async fn fetch_read_proof() {
let node_process = test_node_process().await;
let client = node_process.client();
let block_hash = client.rpc().block_hash(None).await.unwrap();
client
.rpc()
.read_proof(
vec![
StorageKey(well_known_keys::HEAP_PAGES.to_vec()),
StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()),
],
block_hash,
)
.await
.unwrap();
}
#[tokio::test]
async fn chain_subscribe_blocks() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut blocks = client.rpc().subscribe_blocks().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn chain_subscribe_finalized_blocks() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut blocks = client.rpc().subscribe_finalized_blocks().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn fetch_keys() {
let node_process = test_node_process().await;
let client = node_process.client();
let keys = client
.storage()
.fetch_keys::<system::storage::Account>(4, None, None)
.await
.unwrap();
assert_eq!(keys.len(), 4)
}
#[tokio::test]
async fn test_iter() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut iter = client
.storage()
.iter::<system::storage::Account>(None)
.await
.unwrap();
let mut i = 0;
while iter.next().await.unwrap().is_some() {
i += 1;
}
assert_eq!(i, 13);
}
#[tokio::test]
async fn fetch_system_info() {
let node_process = test_node_process().await;
let client = node_process.client();
assert_eq!(client.rpc().system_chain().await.unwrap(), "Development");
assert_eq!(client.rpc().system_name().await.unwrap(), "Substrate Node");
assert!(!client.rpc().system_version().await.unwrap().is_empty());
}
#[tokio::test]
async fn dry_run_passes() {
let node_process = test_node_process().await;
let client = node_process.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let bob_address = bob.account_id().clone().into();
let cxt = test_context().await;
let api = &cxt.api;
let signed_extrinsic = api
.tx()
.balances()
.transfer(bob_address, 10_000)
.unwrap()
.create_signed(&alice, Default::default())
.await
.unwrap();
client
.rpc()
.dry_run(signed_extrinsic.encoded(), None)
.await
.expect("dryrunning failed")
.expect("expected dryrunning to be successful")
.unwrap();
signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized_success()
.await
.unwrap();
}
#[tokio::test]
async fn dry_run_fails() {
let node_process = test_node_process().await;
let client = node_process.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let hans = pair_signer(Pair::generate().0);
let hans_address = hans.account_id().clone().into();
let cxt = test_context().await;
let api = &cxt.api;
let signed_extrinsic = api
.tx()
.balances()
.transfer(
hans_address,
100_000_000_000_000_000_000_000_000_000_000_000,
)
.unwrap()
.create_signed(&alice, Default::default())
.await
.unwrap();
let dry_run_res: DispatchOutcome = client
.rpc()
.dry_run(signed_extrinsic.encoded(), None)
.await
.expect("dryrunning failed")
.expect("expected dryrun transaction to be valid");
if let Err(sp_runtime::DispatchError::Module(module_error)) = dry_run_res {
assert_eq!(module_error.index, 6);
assert_eq!(module_error.error, 2);
} else {
panic!("expected a module error when dryrunning");
}
let res = signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized_success()
.await;
if let Err(Error::Module(err)) = res {
assert_eq!(err.pallet, "Balances");
assert_eq!(err.error, "InsufficientBalance");
} else {
panic!("expected a runtime module error");
}
}
+2 -2
View File
@@ -22,7 +22,7 @@ integration-tests = []
bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] }
scale-info = { version = "2.0.0", features = ["bit-vec"] }
scale-value = "0.2.0"
scale-value = "0.4.0"
futures = "0.3.13"
hex = "0.4.3"
jsonrpsee = { version = "0.15.1", features = ["async-client", "client-ws-transport"] }
@@ -42,4 +42,4 @@ frame-metadata = "15.0.0"
derivative = "2.2.0"
[dev-dependencies]
tokio = { version = "1.8", features = ["macros", "time"] }
tokio = { version = "1.8", features = ["macros", "time", "rt-multi-thread"] }
-503
View File
@@ -1,503 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use futures::future;
pub use sp_runtime::traits::SignedExtension;
use sp_runtime::{
traits::Hash,
ApplyExtrinsicResult,
};
use crate::{
error::{
BasicError,
HasModuleError,
},
extrinsic::{
ExtrinsicParams,
Signer,
},
rpc::{
Rpc,
RpcClient,
RuntimeVersion,
SystemProperties,
},
storage::StorageClient,
transaction::TransactionProgress,
updates::UpdateClient,
Call,
Config,
Encoded,
Metadata,
};
use codec::{
Compact,
Decode,
Encode,
};
use derivative::Derivative;
use parking_lot::RwLock;
use std::sync::Arc;
/// ClientBuilder for constructing a Client.
#[derive(Default)]
pub struct ClientBuilder {
url: Option<String>,
client: Option<RpcClient>,
metadata: Option<Metadata>,
page_size: Option<u32>,
}
impl ClientBuilder {
/// Creates a new ClientBuilder.
pub fn new() -> Self {
Self {
url: None,
client: None,
metadata: None,
page_size: None,
}
}
/// Sets the jsonrpsee client.
pub fn set_client<C: Into<RpcClient>>(mut self, client: C) -> Self {
self.client = Some(client.into());
self
}
/// Set the substrate rpc address.
pub fn set_url<P: Into<String>>(mut self, url: P) -> Self {
self.url = Some(url.into());
self
}
/// Set the page size.
pub fn set_page_size(mut self, size: u32) -> Self {
self.page_size = Some(size);
self
}
/// Set the metadata.
///
/// *Note:* Metadata will no longer be downloaded from the runtime node.
#[cfg(feature = "integration-tests")]
pub fn set_metadata(mut self, metadata: Metadata) -> Self {
self.metadata = Some(metadata);
self
}
/// Builder for [Client].
///
/// # Examples
///
/// ```no_run
/// use subxt::{ClientBuilder, DefaultConfig};
///
/// #[tokio::main]
/// async fn main() {
/// // Build the client.
/// let client = ClientBuilder::new()
/// .set_url("wss://rpc.polkadot.io:443")
/// .build::<DefaultConfig>()
/// .await
/// .unwrap();
/// // Use the client...
/// }
/// ```
pub async fn build<T: Config>(self) -> Result<Client<T>, BasicError> {
let client = if let Some(client) = self.client {
client
} else {
let url = self.url.as_deref().unwrap_or("ws://127.0.0.1:9944");
crate::rpc::ws_client(url).await?
};
let rpc = Rpc::new(client);
let (genesis_hash, runtime_version, properties) = future::join3(
rpc.genesis_hash(),
rpc.runtime_version(None),
rpc.system_properties(),
)
.await;
let metadata = if let Some(metadata) = self.metadata {
metadata
} else {
rpc.metadata().await?
};
Ok(Client {
rpc,
genesis_hash: genesis_hash?,
metadata: Arc::new(RwLock::new(metadata)),
properties: properties.unwrap_or_else(|_| Default::default()),
runtime_version: Arc::new(RwLock::new(runtime_version?)),
iter_page_size: self.page_size.unwrap_or(10),
})
}
}
/// Client to interface with a substrate node.
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Client<T: Config> {
rpc: Rpc<T>,
genesis_hash: T::Hash,
metadata: Arc<RwLock<Metadata>>,
properties: SystemProperties,
runtime_version: Arc<RwLock<RuntimeVersion>>,
iter_page_size: u32,
}
impl<T: Config> std::fmt::Debug for Client<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("rpc", &"<Rpc>")
.field("genesis_hash", &self.genesis_hash)
.field("metadata", &"<Metadata>")
.field("events_decoder", &"<EventsDecoder>")
.field("properties", &self.properties)
.field("runtime_version", &self.runtime_version)
.field("iter_page_size", &self.iter_page_size)
.finish()
}
}
impl<T: Config> Client<T> {
/// Returns the genesis hash.
pub fn genesis(&self) -> &T::Hash {
&self.genesis_hash
}
/// Returns the chain metadata.
pub fn metadata(&self) -> Arc<RwLock<Metadata>> {
Arc::clone(&self.metadata)
}
/// Returns the properties defined in the chain spec as a JSON object.
///
/// # Note
///
/// Many chains use this to define common properties such as `token_decimals` and `token_symbol`
/// required for UIs, but this is merely a convention. It is up to the library user to
/// deserialize the JSON into the appropriate type or otherwise extract the properties defined
/// in the target chain's spec.
pub fn properties(&self) -> &SystemProperties {
&self.properties
}
/// Returns the rpc client.
pub fn rpc(&self) -> &Rpc<T> {
&self.rpc
}
/// Create a client for accessing runtime storage
pub fn storage(&self) -> StorageClient<T> {
StorageClient::new(&self.rpc, self.metadata(), self.iter_page_size)
}
/// Create a wrapper for performing runtime updates on this client.
///
/// # Note
///
/// The update client is intended to be used in the background for
/// performing runtime updates, while the API is still in use.
/// Without performing runtime updates the submitted extrinsics may fail.
///
/// # Examples
///
/// ```no_run
/// # use subxt::{ClientBuilder, DefaultConfig};
/// #
/// # #[tokio::main]
/// # async fn main() {
/// # let client = ClientBuilder::new()
/// # .set_url("wss://rpc.polkadot.io:443")
/// # .build::<DefaultConfig>()
/// # .await
/// # .unwrap();
/// #
/// let update_client = client.updates();
/// // Spawn a new background task to handle runtime updates.
/// tokio::spawn(async move {
/// let result = update_client.perform_runtime_updates().await;
/// println!("Runtime update finished with result={:?}", result);
/// });
/// # }
/// ```
pub fn updates(&self) -> UpdateClient<T> {
UpdateClient::new(
self.rpc.clone(),
self.metadata(),
self.runtime_version.clone(),
)
}
/// Convert the client to a runtime api wrapper for custom runtime access.
///
/// The `subxt` proc macro will provide methods to submit extrinsics and read storage specific
/// to the target runtime.
pub fn to_runtime_api<R: From<Self>>(self) -> R {
self.into()
}
/// Returns the client's Runtime Version.
pub fn runtime_version(&self) -> Arc<RwLock<RuntimeVersion>> {
Arc::clone(&self.runtime_version)
}
}
/// A constructed call ready to be signed and submitted.
pub struct SubmittableExtrinsic<'client, T: Config, X, C, E: Decode, Evs: Decode> {
client: &'client Client<T>,
call: C,
marker: std::marker::PhantomData<(X, E, Evs)>,
}
impl<'client, T, X, C, E, Evs> SubmittableExtrinsic<'client, T, X, C, E, Evs>
where
T: Config,
X: ExtrinsicParams<T>,
C: Call + Send + Sync,
E: Decode + HasModuleError,
Evs: Decode,
{
/// Create a new [`SubmittableExtrinsic`].
pub fn new(client: &'client Client<T>, call: C) -> Self {
Self {
client,
call,
marker: Default::default(),
}
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
///
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch_default(
&self,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError>
where
X::OtherParams: Default,
{
self.sign_and_submit_then_watch(signer, Default::default())
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
///
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch(
&self,
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError> {
self.create_signed(signer, other_params)
.await?
.submit_and_watch()
.await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default(
&self,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<T::Hash, BasicError>
where
X::OtherParams: Default,
{
self.sign_and_submit(signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit(
&self,
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<T::Hash, BasicError> {
self.create_signed(signer, other_params)
.await?
.submit()
.await
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data(&self) -> Result<Vec<u8>, BasicError> {
let mut bytes = Vec::new();
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
let pallet = metadata.pallet(C::PALLET)?;
bytes.push(pallet.index());
bytes.push(pallet.call_index::<C>()?);
self.call.encode_to(&mut bytes);
Ok(bytes)
}
/// Creates a returns a raw signed extrinsic, without submitting it.
pub async fn create_signed(
&self,
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<SignedSubmittableExtrinsic<'client, T, X, E, Evs>, BasicError> {
// 1. Get nonce
let account_nonce = if let Some(nonce) = signer.nonce() {
nonce
} else {
self.client
.rpc()
.system_account_next_index(signer.account_id())
.await?
};
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = Encoded(self.call_data()?);
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = {
// Obtain spec version and transaction version from the runtime version of the client.
let locked_runtime = self.client.runtime_version();
let runtime = locked_runtime.read();
X::new(
runtime.spec_version,
runtime.transaction_version,
account_nonce,
self.client.genesis_hash,
other_params,
)
};
tracing::debug!(
"additional_and_extra_params: {:?}",
additional_and_extra_params
);
// 4. Construct signature. This is compatible with the Encode impl
// for SignedPayload (which is this payload of bytes that we'd like)
// to sign. See:
// https://github.com/paritytech/substrate/blob/9a6d706d8db00abb6ba183839ec98ecd9924b1f8/primitives/runtime/src/generic/unchecked_extrinsic.rs#L215)
let signature = {
let mut bytes = Vec::new();
call_data.encode_to(&mut bytes);
additional_and_extra_params.encode_extra_to(&mut bytes);
additional_and_extra_params.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
signer.sign(&sp_core::blake2_256(&bytes))
} else {
signer.sign(&bytes)
}
};
tracing::info!("xt signature: {}", hex::encode(signature.encode()));
// 5. Encode extrinsic, now that we have the parts we need. This is compatible
// with the Encode impl for UncheckedExtrinsic (protocol version 4).
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
signer.address().encode_to(&mut encoded_inner);
// the signature bytes
signature.encode_to(&mut encoded_inner);
// attach custom extra params
additional_and_extra_params.encode_extra_to(&mut encoded_inner);
// and now, call data
call_data.encode_to(&mut encoded_inner);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
.expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
// maybe we can just return the raw bytes..
Ok(SignedSubmittableExtrinsic {
client: self.client,
encoded: Encoded(extrinsic),
marker: self.marker,
})
}
}
pub struct SignedSubmittableExtrinsic<'client, T: Config, X, E: Decode, Evs: Decode> {
client: &'client Client<T>,
encoded: Encoded,
marker: std::marker::PhantomData<(X, E, Evs)>,
}
impl<'client, T, X, E, Evs> SignedSubmittableExtrinsic<'client, T, X, E, Evs>
where
T: Config,
X: ExtrinsicParams<T>,
E: Decode + HasModuleError,
Evs: Decode,
{
/// Submits the extrinsic to the chain.
///
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn submit_and_watch(
&self,
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError> {
// Get a hash of the extrinsic (we'll need this later).
let ext_hash = T::Hashing::hash_of(&self.encoded);
// Submit and watch for transaction progress.
let sub = self.client.rpc().watch_extrinsic(&self.encoded).await?;
Ok(TransactionProgress::new(sub, self.client, ext_hash))
}
/// Submits the extrinsic to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn submit(&self) -> Result<T::Hash, BasicError> {
self.client.rpc().submit_extrinsic(&self.encoded).await
}
/// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
///
/// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
pub async fn dry_run(
&self,
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, BasicError> {
self.client.rpc().dry_run(self.encoded(), at).await
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module provides two clients that can be used to work with
//! transactions, storage and events. The [`OfflineClient`] works
//! entirely offline and can be passed to any function that doesn't
//! require network access. The [`OnlineClient`] requires network
//! access.
mod offline_client;
mod online_client;
pub use offline_client::{
OfflineClient,
OfflineClientT,
};
pub use online_client::{
OnlineClient,
OnlineClientT,
};
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
constants::ConstantsClient,
events::EventsClient,
rpc::RuntimeVersion,
storage::StorageClient,
tx::TxClient,
Config,
Metadata,
};
use derivative::Derivative;
use std::sync::Arc;
/// A trait representing a client that can perform
/// offline-only actions.
pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
/// Return the provided [`Metadata`].
fn metadata(&self) -> Metadata;
/// Return the provided genesis hash.
fn genesis_hash(&self) -> T::Hash;
/// Return the provided [`RuntimeVersion`].
fn runtime_version(&self) -> RuntimeVersion;
/// Work with transactions.
fn tx(&self) -> TxClient<T, Self> {
TxClient::new(self.clone())
}
/// Work with events.
fn events(&self) -> EventsClient<T, Self> {
EventsClient::new(self.clone())
}
/// Work with storage.
fn storage(&self) -> StorageClient<T, Self> {
StorageClient::new(self.clone())
}
/// Access constants.
fn constants(&self) -> ConstantsClient<T, Self> {
ConstantsClient::new(self.clone())
}
}
/// A client that is capable of performing offline-only operations.
/// Can be constructed as long as you can populate the required fields.
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
pub struct OfflineClient<T: Config> {
inner: Arc<Inner<T>>,
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
struct Inner<T: Config> {
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<T: Config> OfflineClient<T> {
/// Construct a new [`OfflineClient`], providing
/// the necessary runtime and compile-time arguments.
pub fn new(
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
) -> OfflineClient<T> {
OfflineClient {
inner: Arc::new(Inner {
genesis_hash,
runtime_version,
metadata,
}),
}
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
self.inner.genesis_hash
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
self.inner.runtime_version.clone()
}
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
self.inner.metadata.clone()
}
// Just a copy of the most important trait methods so that people
// don't need to import the trait for most things:
/// Work with transactions.
pub fn tx(&self) -> TxClient<T, Self> {
<Self as OfflineClientT<T>>::tx(self)
}
/// Work with events.
pub fn events(&self) -> EventsClient<T, Self> {
<Self as OfflineClientT<T>>::events(self)
}
/// Work with storage.
pub fn storage(&self) -> StorageClient<T, Self> {
<Self as OfflineClientT<T>>::storage(self)
}
/// Access constants.
pub fn constants(&self) -> ConstantsClient<T, Self> {
<Self as OfflineClientT<T>>::constants(self)
}
}
impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
fn genesis_hash(&self) -> T::Hash {
self.genesis_hash()
}
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
fn metadata(&self) -> Metadata {
self.metadata()
}
}
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
// so this allows users to pass references to a client rather than explicitly
// cloning. This is partly for consistency with OnlineClient, which can be
// easily converted into an OfflineClient for ergonomics.
impl<'a, T: Config> From<&'a OfflineClient<T>> for OfflineClient<T> {
fn from(c: &'a OfflineClient<T>) -> Self {
c.clone()
}
}
+234
View File
@@ -0,0 +1,234 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{
OfflineClient,
OfflineClientT,
};
use crate::{
constants::ConstantsClient,
error::Error,
events::EventsClient,
rpc::{
Rpc,
RpcClient,
RuntimeVersion,
},
storage::StorageClient,
tx::TxClient,
Config,
Metadata,
};
use derivative::Derivative;
use futures::future;
use parking_lot::RwLock;
use std::sync::Arc;
/// A trait representing a client that can perform
/// online actions.
pub trait OnlineClientT<T: Config>: OfflineClientT<T> {
/// Return an RPC client that can be used to communicate with a node.
fn rpc(&self) -> &Rpc<T>;
}
/// A client that can be used to perform API calls (that is, either those
/// requiriing an [`OfflineClientT`] or those requiring an [`OnlineClientT`]).
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct OnlineClient<T: Config> {
inner: Arc<RwLock<Inner<T>>>,
rpc: Rpc<T>,
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct Inner<T: Config> {
genesis_hash: T::Hash,
runtime_version: RuntimeVersion,
metadata: Metadata,
}
impl<T: Config> std::fmt::Debug for OnlineClient<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("rpc", &"<Rpc>")
.field("inner", &self.inner)
.finish()
}
}
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] using default settings which
/// point to a locally running node on `ws://127.0.0.1:9944`.
pub async fn new() -> Result<OnlineClient<T>, Error> {
let url = "ws://127.0.0.1:9944";
OnlineClient::from_url(url).await
}
/// Construct a new [`OnlineClient`], providing a URL to connect to.
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
let client = crate::rpc::ws_client(url.as_ref()).await?;
OnlineClient::from_rpc_client(client).await
}
/// Construct a new [`OnlineClient`] by providing the underlying [`RpcClient`]
/// to use to drive the connection.
pub async fn from_rpc_client(
rpc_client: impl Into<RpcClient>,
) -> Result<OnlineClient<T>, Error> {
let rpc = Rpc::new(rpc_client.into());
let (genesis_hash, runtime_version, metadata) = future::join3(
rpc.genesis_hash(),
rpc.runtime_version(None),
rpc.metadata(),
)
.await;
Ok(OnlineClient {
inner: Arc::new(RwLock::new(Inner {
genesis_hash: genesis_hash?,
runtime_version: runtime_version?,
metadata: metadata?,
})),
rpc,
})
}
/// Create an object which can be used to keep the runtime uptodate
/// in a separate thread.
///
/// # Example
///
/// ```no_run
/// # #[tokio::main]
/// # async fn main() {
/// use subxt::{ OnlineClient, PolkadotConfig };
///
/// let client = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// let update_task = client.subscribe_to_updates();
/// tokio::spawn(async move {
/// update_task.perform_runtime_updates().await;
/// });
/// # }
/// ```
pub fn subscribe_to_updates(&self) -> ClientRuntimeUpdater<T> {
ClientRuntimeUpdater(self.clone())
}
/// Return the [`Metadata`] used in this client.
pub fn metadata(&self) -> Metadata {
let inner = self.inner.read();
inner.metadata.clone()
}
/// Return the genesis hash.
pub fn genesis_hash(&self) -> T::Hash {
let inner = self.inner.read();
inner.genesis_hash
}
/// Return the runtime version.
pub fn runtime_version(&self) -> RuntimeVersion {
let inner = self.inner.read();
inner.runtime_version.clone()
}
/// Return an RPC client to make raw requests with.
pub fn rpc(&self) -> &Rpc<T> {
&self.rpc
}
/// Return an offline client with the same configuration as this.
pub fn offline(&self) -> OfflineClient<T> {
let inner = self.inner.read();
OfflineClient::new(
inner.genesis_hash,
inner.runtime_version.clone(),
inner.metadata.clone(),
)
}
// Just a copy of the most important trait methods so that people
// don't need to import the trait for most things:
/// Work with transactions.
pub fn tx(&self) -> TxClient<T, Self> {
<Self as OfflineClientT<T>>::tx(self)
}
/// Work with events.
pub fn events(&self) -> EventsClient<T, Self> {
<Self as OfflineClientT<T>>::events(self)
}
/// Work with storage.
pub fn storage(&self) -> StorageClient<T, Self> {
<Self as OfflineClientT<T>>::storage(self)
}
/// Access constants.
pub fn constants(&self) -> ConstantsClient<T, Self> {
<Self as OfflineClientT<T>>::constants(self)
}
}
impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
fn metadata(&self) -> Metadata {
self.metadata()
}
fn genesis_hash(&self) -> T::Hash {
self.genesis_hash()
}
fn runtime_version(&self) -> RuntimeVersion {
self.runtime_version()
}
}
impl<T: Config> OnlineClientT<T> for OnlineClient<T> {
fn rpc(&self) -> &Rpc<T> {
&self.rpc
}
}
/// Client wrapper for performing runtime updates. See [`OnlineClient::subscribe_to_updates()`]
/// for example usage.
pub struct ClientRuntimeUpdater<T: Config>(OnlineClient<T>);
impl<T: Config> ClientRuntimeUpdater<T> {
fn is_runtime_version_different(&self, new: &RuntimeVersion) -> bool {
let curr = self.0.inner.read();
&curr.runtime_version != new
}
/// Performs runtime updates indefinitely unless encountering an error.
///
/// *Note:* This will run indefinitely until it errors, so the typical usage
/// would be to run it in a separate background task.
pub async fn perform_runtime_updates(&self) -> Result<(), Error> {
// Obtain an update subscription to further detect changes in the runtime version of the node.
let mut update_subscription = self.0.rpc.subscribe_runtime_version().await?;
while let Some(new_runtime_version) = update_subscription.next().await {
// The Runtime Version obtained via subscription.
let new_runtime_version = new_runtime_version?;
// Ignore this update if there is no difference.
if !self.is_runtime_version_different(&new_runtime_version) {
continue
}
// Fetch new metadata.
let new_metadata = self.0.rpc.metadata().await?;
// Do the update.
let mut writable = self.0.inner.write();
writable.metadata = new_metadata;
writable.runtime_version = new_runtime_version;
}
Ok(())
}
}
+54 -4
View File
@@ -2,6 +2,12 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module provides a [`Config`] type, which is used to define various
//! types that are important in order to speak to a particular chain.
//! [`SubstrateConfig`] provides a default set of these types suitable for the
//! default Substrate node implementation, and [`PolkadotConfig`] for a
//! Polkadot node.
use codec::{
Codec,
Encode,
@@ -22,7 +28,7 @@ use sp_runtime::traits::{
// Note: the 'static bound isn't strictly required, but currently deriving TypeInfo
// automatically applies a 'static bound to all generic types (including this one),
// and so until that is resolved, we'll keep the (easy to satisfy) constraint here.
pub trait Config: Debug + 'static {
pub trait Config: 'static {
/// Account index (aka nonce) type. This stores the number of previous
/// transactions associated with a sender account.
type Index: Parameter
@@ -74,6 +80,9 @@ pub trait Config: Debug + 'static {
/// Extrinsic type within blocks.
type Extrinsic: Parameter + Extrinsic + Debug + MaybeSerializeDeserialize;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: crate::tx::ExtrinsicParams<Self::Index, Self::Hash>;
}
/// Parameter trait copied from `substrate::frame_support`
@@ -83,10 +92,9 @@ impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug {}
/// Default set of commonly used types by Substrate runtimes.
// Note: We only use this at the type level, so it should be impossible to
// create an instance of it.
#[derive(Debug)]
pub enum DefaultConfig {}
pub enum SubstrateConfig {}
impl Config for DefaultConfig {
impl Config for SubstrateConfig {
type Index = u32;
type BlockNumber = u32;
type Hash = sp_core::H256;
@@ -97,4 +105,46 @@ impl Config for DefaultConfig {
sp_runtime::generic::Header<Self::BlockNumber, sp_runtime::traits::BlakeTwo256>;
type Signature = sp_runtime::MultiSignature;
type Extrinsic = sp_runtime::OpaqueExtrinsic;
type ExtrinsicParams = crate::tx::SubstrateExtrinsicParams<Self>;
}
/// Default set of commonly used types by Polkadot nodes.
pub type PolkadotConfig = WithExtrinsicParams<
SubstrateConfig,
crate::tx::PolkadotExtrinsicParams<SubstrateConfig>,
>;
/// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the
/// additional and extra parameters to pass to an extrinsic (see [`crate::tx::ExtrinsicParams`]),
/// and returns a type implementing [`Config`] with those new `ExtrinsicParams`.
///
/// # Example
///
/// ```
/// use subxt::config::{ SubstrateConfig, WithExtrinsicParams };
/// use subxt::tx::PolkadotExtrinsicParams;
///
/// // This is how PolkadotConfig is implemented:
/// type PolkadotConfig = WithExtrinsicParams<SubstrateConfig, PolkadotExtrinsicParams<SubstrateConfig>>;
/// ```
pub struct WithExtrinsicParams<
T: Config,
E: crate::tx::ExtrinsicParams<T::Index, T::Hash>,
> {
_marker: std::marker::PhantomData<(T, E)>,
}
impl<T: Config, E: crate::tx::ExtrinsicParams<T::Index, T::Hash>> Config
for WithExtrinsicParams<T, E>
{
type Index = T::Index;
type BlockNumber = T::BlockNumber;
type Hash = T::Hash;
type Hashing = T::Hashing;
type AccountId = T::AccountId;
type Address = T::Address;
type Header = T::Header;
type Signature = T::Signature;
type Extrinsic = T::Extrinsic;
type ExtrinsicParams = E;
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::DecodedValue,
metadata::DecodeWithMetadata,
};
use std::borrow::Cow;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait ConstantAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
/// The name of the constant in a given pallet.
fn constant_name(&self) -> &str;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents a statically generated constant lookup address.
pub struct StaticConstantAddress<ReturnTy> {
pallet_name: &'static str,
constant_name: &'static str,
constant_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<ReturnTy>,
}
impl<ReturnTy> StaticConstantAddress<ReturnTy> {
/// Create a new [`StaticConstantAddress`] that will be validated
/// against node metadata using the hash given.
pub fn new(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name,
constant_name,
constant_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this constant prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
pallet_name: self.pallet_name,
constant_name: self.constant_name,
constant_hash: None,
_marker: self._marker,
}
}
}
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for StaticConstantAddress<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn constant_name(&self) -> &str {
self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// This represents a dynamically generated constant address.
pub struct DynamicConstantAddress<'a> {
pallet_name: Cow<'a, str>,
constant_name: Cow<'a, str>,
}
/// Construct a new dynamic constant lookup.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
constant_name: impl Into<Cow<'a, str>>,
) -> DynamicConstantAddress<'a> {
DynamicConstantAddress {
pallet_name: pallet_name.into(),
constant_name: constant_name.into(),
}
}
impl<'a> ConstantAddress for DynamicConstantAddress<'a> {
type Target = DecodedValue;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn constant_name(&self) -> &str {
&self.constant_name
}
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::ConstantAddress;
use crate::{
client::OfflineClientT,
error::Error,
metadata::{
DecodeWithMetadata,
MetadataError,
},
Config,
};
use derivative::Derivative;
/// A client for accessing constants.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct ConstantsClient<T, Client> {
client: Client,
_marker: std::marker::PhantomData<T>,
}
impl<T, Client> ConstantsClient<T, Client> {
/// Create a new [`ConstantsClient`].
pub fn new(client: Client) -> Self {
Self {
client,
_marker: std::marker::PhantomData,
}
}
}
impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
/// Run the validation logic against some constant address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or constant in question do not exist at all).
pub fn validate<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<(), Error> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = self
.client
.metadata()
.constant_hash(address.pallet_name(), address.constant_name())?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleMetadata.into())
}
}
Ok(())
}
/// Access the constant at the address given, returning the type defined by this address.
/// This is probably used with addresses given from static codegen, although you can manually
/// construct your own, too.
pub fn at<Address: ConstantAddress>(
&self,
address: &Address,
) -> Result<<Address::Target as DecodeWithMetadata>::Target, Error> {
let metadata = self.client.metadata();
// 1. Validate constant shape if hash given:
self.validate(address)?;
// 2. Attempt to decode the constant into the type given:
let pallet = metadata.pallet(address.pallet_name())?;
let constant = pallet.constant(address.constant_name())?;
let value = Address::Target::decode_with_metadata(
&mut &*constant.value,
constant.ty.id(),
&metadata,
)?;
Ok(value)
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing constants.
mod constant_address;
mod constants_client;
pub use constant_address::{
dynamic,
ConstantAddress,
DynamicConstantAddress,
StaticConstantAddress,
};
pub use constants_client::ConstantsClient;
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module provides the entry points to create dynamic
//! transactions, storage and constant lookups.
pub use scale_value::Value;
/// A [`scale_value::Value`] type endowed with contextual information
/// regarding what type was used to decode each part of it. This implements
/// [`crate::metadata::DecodeWithMetadata`], and is used as a return type
/// for dynamic requests.
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
// Submit dynamic transactions.
pub use crate::tx::dynamic as tx;
// Lookup constants dynamically.
pub use crate::constants::dynamic as constant;
// Lookup storage values dynamically.
pub use crate::storage::{
dynamic as storage,
dynamic_root as storage_root,
};
+191 -89
View File
@@ -2,27 +2,32 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::metadata::{
//! Types representing the errors that can be returned.
use crate::metadata::Metadata;
use codec::Decode;
use core::fmt::Debug;
use scale_info::TypeDef;
use std::borrow::Cow;
// Re-expose the errors we use from other crates here:
pub use crate::metadata::{
InvalidMetadataError,
MetadataError,
};
use core::fmt::Debug;
use jsonrpsee::core::error::Error as RequestError;
use scale_value::scale::DecodeError;
use sp_core::crypto::SecretStringError;
use sp_runtime::transaction_validity::TransactionValidityError;
/// An error that may contain some runtime error `E`
pub type Error<E> = GenericError<RuntimeError<E>>;
/// An error that will never contain a runtime error.
pub type BasicError = GenericError<std::convert::Infallible>;
pub use jsonrpsee::core::error::Error as RequestError;
pub use scale_value::scale::{
DecodeError,
EncodeError,
};
pub use sp_core::crypto::SecretStringError;
pub use sp_runtime::transaction_validity::TransactionValidityError;
/// The underlying error enum, generic over the type held by the `Runtime`
/// variant. Prefer to use the [`Error<E>`] and [`BasicError`] aliases over
/// variant. Prefer to use the [`Error<E>`] and [`Error`] aliases over
/// using this type directly.
#[derive(Debug, thiserror::Error)]
pub enum GenericError<E> {
pub enum Error {
/// Io error.
#[error("Io error: {0}")]
Io(#[from] std::io::Error),
@@ -49,99 +54,174 @@ pub enum GenericError<E> {
Metadata(#[from] MetadataError),
/// Runtime error.
#[error("Runtime error: {0:?}")]
Runtime(E),
/// Events decoding error.
#[error("Events decoding error: {0}")]
EventsDecoding(#[from] DecodeError),
Runtime(DispatchError),
/// Error decoding to a [`crate::dynamic::Value`].
#[error("Error decoding into dynamic value: {0}")]
DecodeValue(#[from] DecodeError),
/// Error encoding from a [`crate::dynamic::Value`].
#[error("Error encoding from dynamic value: {0}")]
EncodeValue(#[from] EncodeError<()>),
/// Transaction progress error.
#[error("Transaction error: {0}")]
Transaction(#[from] TransactionError),
#[error("Module error: {0}")]
/// An error from the `Module` variant of the generated `DispatchError`.
Module(ModuleError),
/// An error encoding a storage address.
#[error("Error encoding storage address: {0}")]
StorageAddress(#[from] StorageAddressError),
/// Other error.
#[error("Other error: {0}")]
Other(String),
}
impl<E> GenericError<E> {
/// [`GenericError`] is parameterised over the type that it holds in the `Runtime`
/// variant. This function allows us to map the Runtime error contained within (if present)
/// to a different type.
pub fn map_runtime_err<F, NewE>(self, f: F) -> GenericError<NewE>
where
F: FnOnce(E) -> NewE,
{
match self {
GenericError::Io(e) => GenericError::Io(e),
GenericError::Codec(e) => GenericError::Codec(e),
GenericError::Rpc(e) => GenericError::Rpc(e),
GenericError::Serialization(e) => GenericError::Serialization(e),
GenericError::SecretString(e) => GenericError::SecretString(e),
GenericError::Invalid(e) => GenericError::Invalid(e),
GenericError::InvalidMetadata(e) => GenericError::InvalidMetadata(e),
GenericError::Metadata(e) => GenericError::Metadata(e),
GenericError::EventsDecoding(e) => GenericError::EventsDecoding(e),
GenericError::Transaction(e) => GenericError::Transaction(e),
GenericError::Module(e) => GenericError::Module(e),
GenericError::Other(e) => GenericError::Other(e),
// This is the only branch we really care about:
GenericError::Runtime(e) => GenericError::Runtime(f(e)),
}
}
}
impl BasicError {
/// Convert an [`BasicError`] into any
/// arbitrary [`Error<E>`].
pub fn into_error<E>(self) -> Error<E> {
self.map_runtime_err(|e| match e {})
}
}
impl<E> From<BasicError> for Error<E> {
fn from(err: BasicError) -> Self {
err.into_error()
}
}
impl<E> From<SecretStringError> for GenericError<E> {
impl From<SecretStringError> for Error {
fn from(error: SecretStringError) -> Self {
GenericError::SecretString(error)
Error::SecretString(error)
}
}
impl<E> From<TransactionValidityError> for GenericError<E> {
impl From<TransactionValidityError> for Error {
fn from(error: TransactionValidityError) -> Self {
GenericError::Invalid(error)
Error::Invalid(error)
}
}
impl<E> From<&str> for GenericError<E> {
impl From<&str> for Error {
fn from(error: &str) -> Self {
GenericError::Other(error.into())
Error::Other(error.into())
}
}
impl<E> From<String> for GenericError<E> {
impl From<String> for Error {
fn from(error: String) -> Self {
GenericError::Other(error)
Error::Other(error)
}
}
/// This is used in the place of the `E` in [`GenericError<E>`] when we may have a
/// Runtime Error. We use this wrapper so that it is possible to implement
/// `From<Error<Infallible>` for `Error<RuntimeError<E>>`.
///
/// This should not be used as a type; prefer to use the alias [`Error<E>`] when referring
/// to errors which may contain some Runtime error `E`.
#[derive(Clone, Debug, PartialEq)]
pub struct RuntimeError<E>(pub E);
impl From<DispatchError> for Error {
fn from(error: DispatchError) -> Self {
Error::Runtime(error)
}
}
impl<E> RuntimeError<E> {
/// Extract the actual runtime error from this struct.
pub fn inner(self) -> E {
self.0
/// This is our attempt to decode a runtime DispatchError. We either
/// successfully decode it into a [`ModuleError`], or we fail and keep
/// hold of the bytes, which we can attempt to decode if we have an
/// appropriate static type to hand.
#[derive(Debug, thiserror::Error)]
pub enum DispatchError {
/// An error was emitted from a specific pallet/module.
#[error("Module error: {0}")]
Module(ModuleError),
/// Some other error was emitted.
#[error("Undecoded dispatch error: {0:?}")]
Other(Vec<u8>),
}
impl DispatchError {
/// Attempt to decode a runtime DispatchError, returning either the [`ModuleError`] it decodes
/// to, along with additional details on the error, or returning the raw bytes if it could not
/// be decoded.
pub fn decode_from<'a>(bytes: impl Into<Cow<'a, [u8]>>, metadata: &Metadata) -> Self {
let bytes = bytes.into();
let dispatch_error_ty_id = match metadata.dispatch_error_ty() {
Some(id) => id,
None => {
tracing::warn!(
"Can't decode error: sp_runtime::DispatchError was not found in Metadata"
);
return DispatchError::Other(bytes.into_owned())
}
};
let dispatch_error_ty = match metadata.types().resolve(dispatch_error_ty_id) {
Some(ty) => ty,
None => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError type ID doesn't resolve to a known type");
return DispatchError::Other(bytes.into_owned())
}
};
let variant = match dispatch_error_ty.type_def() {
TypeDef::Variant(var) => var,
_ => {
tracing::warn!(
"Can't decode error: sp_runtime::DispatchError type is not a Variant"
);
return DispatchError::Other(bytes.into_owned())
}
};
let module_variant_idx = variant
.variants()
.iter()
.find(|v| v.name() == "Module")
.map(|v| v.index());
let module_variant_idx = match module_variant_idx {
Some(idx) => idx,
None => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError does not have a 'Module' variant");
return DispatchError::Other(bytes.into_owned())
}
};
// If the error bytes don't correspond to a ModuleError, just return the bytes.
// This is perfectly reasonable and expected, so no logging.
if bytes[0] != module_variant_idx {
return DispatchError::Other(bytes.into_owned())
}
// The remaining bytes are the module error, all being well:
let bytes = &bytes[1..];
// The oldest and second oldest type of error decode to this shape:
#[derive(Decode)]
struct LegacyModuleError {
index: u8,
error: u8,
}
// The newer case expands the error for forward compat:
#[derive(Decode)]
struct CurrentModuleError {
index: u8,
error: [u8; 4],
}
// try to decode into the new shape, or the old if that doesn't work
let err = match CurrentModuleError::decode(&mut &*bytes) {
Ok(e) => e,
Err(_) => {
let old_e = match LegacyModuleError::decode(&mut &*bytes) {
Ok(err) => err,
Err(_) => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError does not match known formats");
return DispatchError::Other(bytes.to_vec())
}
};
CurrentModuleError {
index: old_e.index,
error: [old_e.error, 0, 0, 0],
}
}
};
let error_details = match metadata.error(err.index, err.error[0]) {
Ok(details) => details,
Err(_) => {
tracing::warn!("Can't decode error: sp_runtime::DispatchError::Module details do not match known information");
return DispatchError::Other(bytes.to_vec())
}
};
DispatchError::Module(ModuleError {
pallet: error_details.pallet().to_string(),
error: error_details.error().to_string(),
description: error_details.docs().to_vec(),
error_data: ModuleErrorData {
pallet_index: err.index,
error: err.error,
},
})
}
}
@@ -192,11 +272,33 @@ impl ModuleErrorData {
}
}
/// This trait is automatically implemented for the generated `DispatchError`,
/// so that we can pluck out information about the `Module` error variant, if`
/// it exists.
pub trait HasModuleError {
/// If the error has a `Module` variant, return a tuple of the
/// pallet index and error index. Else, return `None`.
fn module_error_data(&self) -> Option<ModuleErrorData>;
/// Something went wrong trying to encode a storage address.
#[derive(Clone, Debug, thiserror::Error)]
pub enum StorageAddressError {
/// Storage map type must be a composite type.
#[error("Storage map type must be a composite type")]
MapTypeMustBeTuple,
/// Storage lookup does not have the expected number of keys.
#[error("Storage lookup requires {expected} keys but got {actual} keys")]
WrongNumberOfKeys {
/// The actual number of keys needed, based on the metadata.
actual: usize,
/// The number of keys provided in the storage address.
expected: usize,
},
/// Storage lookup requires a type that wasn't found in the metadata.
#[error(
"Storage lookup requires type {0} to exist in the metadata, but it was not found"
)]
TypeNotFound(u32),
/// This storage entry in the metadata does not have the correct number of hashers to fields.
#[error(
"Storage entry in metadata does not have the correct number of hashers to fields"
)]
WrongNumberOfHashers {
/// The number of hashers in the metadata for this storage entry.
hashers: usize,
/// The number of fields in the metadata for this storage entry.
fields: usize,
},
}
+65 -135
View File
@@ -5,18 +5,14 @@
//! Subscribing to events.
use crate::{
error::BasicError,
Client,
client::OnlineClientT,
error::Error,
events::EventsClient,
Config,
};
use codec::Decode;
use derivative::Derivative;
use futures::{
future::Either,
stream::{
self,
BoxStream,
},
stream::BoxStream,
Future,
FutureExt,
Stream,
@@ -30,112 +26,16 @@ use std::{
};
pub use super::{
at,
EventDetails,
EventFilter,
Events,
FilterEvents,
RawEventDetails,
};
/// Subscribe to events from blocks.
///
/// **Note:** these blocks haven't necessarily been finalised yet; prefer
/// [`Events::subscribe_finalized()`] if that is important.
///
/// **Note:** This function is hidden from the documentation
/// and is exposed only to be called via the codegen. It may
/// break between minor releases.
#[doc(hidden)]
pub async fn subscribe<T: Config, Evs: Decode + 'static>(
client: &Client<T>,
) -> Result<EventSubscription<EventSub<T::Header>, T, Evs>, BasicError> {
let block_subscription = client.rpc().subscribe_blocks().await?;
Ok(EventSubscription::new(client, block_subscription))
}
/// Subscribe to events from finalized blocks.
///
/// **Note:** This function is hidden from the documentation
/// and is exposed only to be called via the codegen. It may
/// break between minor releases.
#[doc(hidden)]
pub async fn subscribe_finalized<T: Config, Evs: Decode + 'static>(
client: &Client<T>,
) -> Result<EventSubscription<FinalizedEventSub<T::Header>, T, Evs>, BasicError> {
// fetch the last finalised block details immediately, so that we'll get
// events for each block after this one.
let last_finalized_block_hash = client.rpc().finalized_head().await?;
let last_finalized_block_number = client
.rpc()
.header(Some(last_finalized_block_hash))
.await?
.map(|h| (*h.number()).into());
// Fill in any gaps between the block above and the finalized blocks reported.
let block_subscription = subscribe_to_block_headers_filling_in_gaps(
client,
last_finalized_block_number,
client.rpc().subscribe_finalized_blocks().await?,
);
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
}
/// Take a subscription that returns block headers, and if any block numbers are missed out
/// betweem the block number provided and what's returned from the subscription, we fill in
/// the gaps and get hold of all intermediate block headers.
///
/// **Note:** This is exposed so that we can run integration tests on it, but otherwise
/// should not be used directly and may break between minor releases.
#[doc(hidden)]
pub fn subscribe_to_block_headers_filling_in_gaps<'a, S, E, T: Config>(
client: &'a Client<T>,
mut last_block_num: Option<u64>,
sub: S,
) -> impl Stream<Item = Result<T::Header, BasicError>> + Send + 'a
where
S: Stream<Item = Result<T::Header, E>> + Send + 'a,
E: Into<BasicError> + Send + 'static,
{
sub.flat_map(move |s| {
// Get the header, or return a stream containing just the error. Our EventSubscription
// stream will return `None` as soon as it hits an error like this.
let header = match s {
Ok(header) => header,
Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
};
// We want all previous details up to, but not including this current block num.
let end_block_num = (*header.number()).into();
// This is one after the last block we returned details for last time.
let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
// Iterate over all of the previous blocks we need headers for, ignoring the current block
// (which we already have the header info for):
let previous_headers = stream::iter(start_block_num..end_block_num)
.then(move |n| {
async move {
let hash = client.rpc().block_hash(Some(n.into())).await?;
let header = client.rpc().header(hash).await?;
Ok::<_, BasicError>(header)
}
})
.filter_map(|h| async { h.transpose() });
// On the next iteration, we'll get details starting just after this end block.
last_block_num = Some(end_block_num);
// Return a combination of any previous headers plus the new header.
Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
})
}
/// A `jsonrpsee` Subscription. This forms a part of the `EventSubscription` type handed back
/// in codegen from `subscribe_finalized`, and is exposed to be used in codegen.
#[doc(hidden)]
pub type FinalizedEventSub<'a, Header> = BoxStream<'a, Result<Header, BasicError>>;
pub type FinalizedEventSub<Header> = BoxStream<'static, Result<Header, Error>>;
/// A `jsonrpsee` Subscription. This forms a part of the `EventSubscription` type handed back
/// in codegen from `subscribe`, and is exposed to be used in codegen.
@@ -144,52 +44,80 @@ pub type EventSub<Item> = Subscription<Item>;
/// A subscription to events that implements [`Stream`], and returns [`Events`] objects for each block.
#[derive(Derivative)]
#[derivative(Debug(bound = "Sub: std::fmt::Debug"))]
pub struct EventSubscription<'a, Sub, T: Config, Evs: 'static> {
#[derivative(Debug(bound = "Sub: std::fmt::Debug, Client: std::fmt::Debug"))]
pub struct EventSubscription<T: Config, Client, Sub> {
finished: bool,
client: &'a Client<T>,
client: Client,
block_header_subscription: Sub,
#[derivative(Debug = "ignore")]
at: Option<
std::pin::Pin<
Box<dyn Future<Output = Result<Events<T, Evs>, BasicError>> + Send + 'a>,
>,
>,
_event_type: std::marker::PhantomData<Evs>,
at: Option<std::pin::Pin<Box<dyn Future<Output = Result<Events<T>, Error>> + Send>>>,
}
impl<'a, Sub, T: Config, Evs: Decode, E: Into<BasicError>>
EventSubscription<'a, Sub, T, Evs>
impl<T: Config, Client, Sub, E: Into<Error>> EventSubscription<T, Client, Sub>
where
Sub: Stream<Item = Result<T::Header, E>> + Unpin + 'a,
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
{
fn new(client: &'a Client<T>, block_header_subscription: Sub) -> Self {
/// Create a new [`EventSubscription`] from a client and a subscription
/// which returns block headers.
pub fn new(client: Client, block_header_subscription: Sub) -> Self {
EventSubscription {
finished: false,
client,
block_header_subscription,
at: None,
_event_type: std::marker::PhantomData,
}
}
/// Return only specific events matching the tuple of 1 or more event
/// types that has been provided as the `Filter` type parameter.
pub fn filter_events<Filter: EventFilter>(self) -> FilterEvents<'a, Self, T, Filter> {
///
/// # Example
///
/// ```no_run
/// use futures::StreamExt;
/// use subxt::{OnlineClient, PolkadotConfig};
///
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
/// pub mod polkadot {}
///
/// # #[tokio::main]
/// # async fn main() {
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// let mut events = api
/// .events()
/// .subscribe()
/// .await
/// .unwrap()
/// .filter_events::<(
/// polkadot::balances::events::Transfer,
/// polkadot::balances::events::Deposit
/// )>();
///
/// while let Some(ev) = events.next().await {
/// let event_details = ev.unwrap();
/// match event_details.event {
/// (Some(transfer), None) => println!("Balance transfer event: {transfer:?}"),
/// (None, Some(deposit)) => println!("Balance deposit event: {deposit:?}"),
/// _ => unreachable!()
/// }
/// }
/// # }
/// ```
pub fn filter_events<Filter: EventFilter>(
self,
) -> FilterEvents<'static, Self, T, Filter> {
FilterEvents::new(self)
}
}
impl<'a, T: Config, Sub: Unpin, Evs: Decode> Unpin
for EventSubscription<'a, Sub, T, Evs>
{
}
impl<T: Config, Client, Sub: Unpin> Unpin for EventSubscription<T, Client, Sub> {}
// We want `EventSubscription` to implement Stream. The below implementation is the rather verbose
// way to roughly implement the following function:
//
// ```
// fn subscribe_events<T: Config, Evs: Decode>(client: &'_ Client<T>, block_sub: Subscription<T::Header>) -> impl Stream<Item=Result<Events<'_, T, Evs>, BasicError>> + '_ {
// fn subscribe_events<T: Config, Evs: Decode>(client: &'_ Client<T>, block_sub: Subscription<T::Header>) -> impl Stream<Item=Result<Events<'_, T, Evs>, Error>> + '_ {
// use futures::StreamExt;
// block_sub.then(move |block_header_res| async move {
// use sp_runtime::traits::Header;
@@ -202,14 +130,14 @@ impl<'a, T: Config, Sub: Unpin, Evs: Decode> Unpin
//
// The advantage of this manual implementation is that we have a named type that we (and others)
// can derive things on, store away, alias etc.
impl<'a, Sub, T, Evs, E> Stream for EventSubscription<'a, Sub, T, Evs>
impl<T, Client, Sub, E> Stream for EventSubscription<T, Client, Sub>
where
T: Config,
Evs: Decode,
Sub: Stream<Item = Result<T::Header, E>> + Unpin + 'a,
E: Into<BasicError>,
Client: OnlineClientT<T>,
Sub: Stream<Item = Result<T::Header, E>> + Unpin,
E: Into<Error>,
{
type Item = Result<Events<T, Evs>, BasicError>;
type Item = Result<Events<T>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
@@ -235,7 +163,9 @@ where
Some(Ok(block_header)) => {
// Note [jsdw]: We may be able to get rid of the per-item allocation
// with https://github.com/oblique/reusable-box-future.
self.at = Some(Box::pin(at(self.client, block_header.hash())));
let at = EventsClient::new(self.client.clone())
.at(Some(block_header.hash()));
self.at = Some(Box::pin(at));
// Continue, so that we poll this function future we've just created.
}
}
@@ -263,16 +193,16 @@ mod test {
fn assert_send<T: Send>() {}
assert_send::<
EventSubscription<
EventSub<<crate::DefaultConfig as Config>::Header>,
crate::DefaultConfig,
crate::SubstrateConfig,
(),
EventSub<<crate::SubstrateConfig as Config>::Header>,
>,
>();
assert_send::<
EventSubscription<
FinalizedEventSub<<crate::DefaultConfig as Config>::Header>,
crate::DefaultConfig,
crate::SubstrateConfig,
(),
FinalizedEventSub<<crate::SubstrateConfig as Config>::Header>,
>,
>();
}
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
client::OnlineClientT,
error::Error,
events::{
EventSub,
EventSubscription,
Events,
FinalizedEventSub,
},
Config,
};
use derivative::Derivative;
use futures::{
future::Either,
stream,
Stream,
StreamExt,
};
use sp_core::{
storage::StorageKey,
twox_128,
};
use sp_runtime::traits::Header;
use std::future::Future;
/// A client for working with events.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct EventsClient<T, Client> {
client: Client,
_marker: std::marker::PhantomData<T>,
}
impl<T, Client> EventsClient<T, Client> {
/// Create a new [`EventsClient`].
pub fn new(client: Client) -> Self {
Self {
client,
_marker: std::marker::PhantomData,
}
}
}
impl<T, Client> EventsClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Obtain events at some block hash.
pub fn at(
&self,
block_hash: Option<T::Hash>,
) -> impl Future<Output = Result<Events<T>, Error>> + Send + 'static {
// Clone and pass the client in like this so that we can explicitly
// return a Future that's Send + 'static, rather than tied to &self.
let client = self.client.clone();
async move { at(client, block_hash).await }
}
/// Subscribe to all events from blocks.
///
/// **Note:** these blocks haven't necessarily been finalised yet; prefer
/// [`EventsClient::subscribe_finalized()`] if that is important.
///
/// # Example
///
/// ```no_run
/// # #[tokio::main]
/// # async fn main() {
/// use futures::StreamExt;
/// use subxt::{ OnlineClient, PolkadotConfig };
///
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// let mut events = api.events().subscribe().await.unwrap();
///
/// while let Some(ev) = events.next().await {
/// // Obtain all events from this block.
/// let ev = ev.unwrap();
/// // Print block hash.
/// println!("Event at block hash {:?}", ev.block_hash());
/// // Iterate over all events.
/// let mut iter = ev.iter();
/// while let Some(event_details) = iter.next() {
/// println!("Event details {:?}", event_details);
/// }
/// }
/// # }
/// ```
pub fn subscribe(
&self,
) -> impl Future<
Output = Result<EventSubscription<T, Client, EventSub<T::Header>>, Error>,
> + Send
+ 'static {
let client = self.client.clone();
async move { subscribe(client).await }
}
/// Subscribe to events from finalized blocks. See [`EventsClient::subscribe()`] for details.
pub fn subscribe_finalized(
&self,
) -> impl Future<
Output = Result<
EventSubscription<T, Client, FinalizedEventSub<T::Header>>,
Error,
>,
> + Send
+ 'static
where
Client: Send + Sync + 'static,
{
let client = self.client.clone();
async move { subscribe_finalized(client).await }
}
}
async fn at<T, Client>(
client: Client,
block_hash: Option<T::Hash>,
) -> Result<Events<T>, Error>
where
T: Config,
Client: OnlineClientT<T>,
{
// If block hash is not provided, get the hash
// for the latest block and use that.
let block_hash = match block_hash {
Some(hash) => hash,
None => {
client
.rpc()
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
}
};
let event_bytes = client
.rpc()
.storage(&*system_events_key().0, Some(block_hash))
.await?
.map(|e| e.0)
.unwrap_or_else(Vec::new);
Ok(Events::new(client.metadata(), block_hash, event_bytes))
}
async fn subscribe<T, Client>(
client: Client,
) -> Result<EventSubscription<T, Client, EventSub<T::Header>>, Error>
where
T: Config,
Client: OnlineClientT<T>,
{
let block_subscription = client.rpc().subscribe_blocks().await?;
Ok(EventSubscription::new(client, block_subscription))
}
/// Subscribe to events from finalized blocks.
async fn subscribe_finalized<T, Client>(
client: Client,
) -> Result<EventSubscription<T, Client, FinalizedEventSub<T::Header>>, Error>
where
T: Config,
Client: OnlineClientT<T>,
{
// fetch the last finalised block details immediately, so that we'll get
// events for each block after this one.
let last_finalized_block_hash = client.rpc().finalized_head().await?;
let last_finalized_block_number = client
.rpc()
.header(Some(last_finalized_block_hash))
.await?
.map(|h| (*h.number()).into());
let sub = client.rpc().subscribe_finalized_blocks().await?;
// Fill in any gaps between the block above and the finalized blocks reported.
let block_subscription = subscribe_to_block_headers_filling_in_gaps(
client.clone(),
last_finalized_block_number,
sub,
);
Ok(EventSubscription::new(client, Box::pin(block_subscription)))
}
/// Note: This is exposed for testing but is not considered stable and may change
/// without notice in a patch release.
#[doc(hidden)]
pub fn subscribe_to_block_headers_filling_in_gaps<T, Client, S, E>(
client: Client,
mut last_block_num: Option<u64>,
sub: S,
) -> impl Stream<Item = Result<T::Header, Error>> + Send
where
T: Config,
Client: OnlineClientT<T> + Send + Sync,
S: Stream<Item = Result<T::Header, E>> + Send,
E: Into<Error> + Send + 'static,
{
sub.flat_map(move |s| {
let client = client.clone();
// Get the header, or return a stream containing just the error. Our EventSubscription
// stream will return `None` as soon as it hits an error like this.
let header = match s {
Ok(header) => header,
Err(e) => return Either::Left(stream::once(async { Err(e.into()) })),
};
// We want all previous details up to, but not including this current block num.
let end_block_num = (*header.number()).into();
// This is one after the last block we returned details for last time.
let start_block_num = last_block_num.map(|n| n + 1).unwrap_or(end_block_num);
// Iterate over all of the previous blocks we need headers for, ignoring the current block
// (which we already have the header info for):
let previous_headers = stream::iter(start_block_num..end_block_num)
.then(move |n| {
let client = client.clone();
async move {
let hash = client.rpc().block_hash(Some(n.into())).await?;
let header = client.rpc().header(hash).await?;
Ok::<_, Error>(header)
}
})
.filter_map(|h| async { h.transpose() });
// On the next iteration, we'll get details starting just after this end block.
last_block_num = Some(end_block_num);
// Return a combination of any previous headers plus the new header.
Either::Right(previous_headers.chain(stream::once(async { Ok(header) })))
})
}
// The storage key needed to access events.
fn system_events_key() -> StorageKey {
let mut storage_key = twox_128(b"System").to_vec();
storage_key.extend(twox_128(b"Events").to_vec());
StorageKey(storage_key)
}
+208 -435
View File
@@ -4,13 +4,15 @@
//! A representation of a block of events.
use crate::{
error::BasicError,
Client,
Config,
Event,
Metadata,
use super::{
Phase,
StaticEvent,
};
use crate::{
dynamic::DecodedValue,
error::Error,
Config,
Metadata,
};
use codec::{
Compact,
@@ -19,77 +21,48 @@ use codec::{
Input,
};
use derivative::Derivative;
use parking_lot::RwLock;
use sp_core::{
storage::StorageKey,
twox_128,
};
use std::sync::Arc;
/// Obtain events at some block hash. The generic parameter is what we
/// will attempt to decode each event into if using [`Events::iter()`],
/// and is expected to be the outermost event enum that contains all of
/// the possible events across all pallets.
///
/// **Note:** This function is hidden from the documentation
/// and is exposed only to be called via the codegen. Thus, prefer to use
/// `api.events().at(block_hash)` over calling this directly.
#[doc(hidden)]
pub async fn at<T: Config, Evs: Decode>(
client: &'_ Client<T>,
block_hash: T::Hash,
) -> Result<Events<T, Evs>, BasicError> {
let mut event_bytes = client
.rpc()
.storage(&system_events_key(), Some(block_hash))
.await?
.map(|s| s.0)
.unwrap_or_else(Vec::new);
// event_bytes is a SCALE encoded vector of events. So, pluck the
// compact encoded length from the front, leaving the remaining bytes
// for our iterating to decode.
//
// Note: if we get no bytes back, avoid an error reading vec length
// and default to 0 events.
let cursor = &mut &*event_bytes;
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
let event_bytes_len = event_bytes.len();
let remaining_len = cursor.len();
event_bytes.drain(0..event_bytes_len - remaining_len);
Ok(Events {
metadata: client.metadata(),
block_hash,
event_bytes,
num_events,
_event_type: std::marker::PhantomData,
})
}
// The storage key needed to access events.
fn system_events_key() -> StorageKey {
let mut storage_key = twox_128(b"System").to_vec();
storage_key.extend(twox_128(b"Events").to_vec());
StorageKey(storage_key)
}
/// A collection of events obtained from a block, bundled with the necessary
/// information needed to decode and iterate over them.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct Events<T: Config, Evs> {
metadata: Arc<RwLock<Metadata>>,
pub struct Events<T: Config> {
metadata: Metadata,
block_hash: T::Hash,
// Note; raw event bytes are prefixed with a Compact<u32> containing
// the number of events to be decoded. We should have stripped that off
// before storing the bytes here.
event_bytes: Vec<u8>,
event_bytes: Arc<[u8]>,
num_events: u32,
_event_type: std::marker::PhantomData<Evs>,
}
impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
impl<T: Config> Events<T> {
pub(crate) fn new(
metadata: Metadata,
block_hash: T::Hash,
mut event_bytes: Vec<u8>,
) -> Self {
// event_bytes is a SCALE encoded vector of events. So, pluck the
// compact encoded length from the front, leaving the remaining bytes
// for our iterating to decode.
//
// Note: if we get no bytes back, avoid an error reading vec length
// and default to 0 events.
let cursor = &mut &*event_bytes;
let num_events = <Compact<u32>>::decode(cursor).unwrap_or(Compact(0)).0;
let event_bytes_len = event_bytes.len();
let remaining_len = cursor.len();
event_bytes.drain(0..event_bytes_len - remaining_len);
Self {
metadata,
block_hash,
event_bytes: event_bytes.into(),
num_events,
}
}
/// The number of events.
pub fn len(&self) -> u32 {
self.num_events
@@ -106,83 +79,23 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
self.block_hash
}
/// Iterate over the events, statically decoding them as we go.
/// If an event is encountered that cannot be statically decoded,
/// a [`codec::Error`] will be returned.
///
/// If the generated code does not know about all of the pallets that exist
/// in the runtime being targeted, it may not know about all of the
/// events either, and so this method should be avoided in favout of [`Events::iter_raw()`],
/// which uses runtime metadata to skip over unknown events.
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<Evs>, BasicError>> + '_ {
let event_bytes = &self.event_bytes;
) -> impl Iterator<Item = Result<EventDetails, Error>> + Send + Sync + 'static {
let event_bytes = self.event_bytes.clone();
let num_events = self.num_events;
let metadata = self.metadata.clone();
let mut pos = 0;
let mut index = 0;
std::iter::from_fn(move || {
let cursor = &mut &event_bytes[pos..];
let start_len = cursor.len();
if start_len == 0 || self.num_events == index {
None
} else {
let mut decode_one_event = || -> Result<_, BasicError> {
let phase = Phase::decode(cursor)?;
let ev = Evs::decode(cursor)?;
let _topics = Vec::<T::Hash>::decode(cursor)?;
Ok((phase, ev))
};
match decode_one_event() {
Ok((phase, event)) => {
// Skip over decoded bytes in next iteration:
pos += start_len - cursor.len();
// Gather the event details before incrementing the index for the next iter.
let res = Some(Ok(EventDetails {
phase,
index,
event,
}));
index += 1;
res
}
Err(e) => {
// By setting the position to the "end" of the event bytes,
// the cursor len will become 0 and the iterator will return `None`
// from now on:
pos = event_bytes.len();
Some(Err(e))
}
}
}
})
}
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
///
/// This method is safe to use even if you do not statically know about
/// all of the possible events; it splits events up using the metadata
/// obtained at runtime, which does.
pub fn iter_raw(
&self,
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + '_ {
let event_bytes = &self.event_bytes;
let metadata = {
let metadata = self.metadata.read();
metadata.clone()
};
let mut pos = 0;
let mut index = 0;
std::iter::from_fn(move || {
let cursor = &mut &event_bytes[pos..];
let start_len = cursor.len();
if start_len == 0 || self.num_events == index {
if start_len == 0 || num_events == index {
None
} else {
match decode_raw_event_details::<T>(&metadata, index, cursor) {
@@ -206,62 +119,11 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
})
}
/// Iterate over all of the events, using metadata to dynamically
/// decode them as we go, and returning the raw bytes and other associated
/// details. If an error occurs, all subsequent iterations return `None`.
///
/// This method is safe to use even if you do not statically know about
/// all of the possible events; it splits events up using the metadata
/// obtained at runtime, which does.
///
/// Unlike [`Events::iter_raw()`] this consumes `self`, which can be useful
/// if you need to store the iterator somewhere and avoid lifetime issues.
pub fn into_iter_raw(
self,
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + 'a {
let mut pos = 0;
let mut index = 0;
let metadata = {
let metadata = self.metadata.read();
metadata.clone()
};
std::iter::from_fn(move || {
let cursor = &mut &self.event_bytes[pos..];
let start_len = cursor.len();
if start_len == 0 || self.num_events == index {
None
} else {
match decode_raw_event_details::<T>(&metadata, index, cursor) {
Ok(raw_event) => {
// Skip over decoded bytes in next iteration:
pos += start_len - cursor.len();
// Increment the index:
index += 1;
// Return the event details:
Some(Ok(raw_event))
}
Err(e) => {
// By setting the position to the "end" of the event bytes,
// the cursor len will become 0 and the iterator will return `None`
// from now on:
pos = self.event_bytes.len();
Some(Err(e))
}
}
}
})
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `Ev` type.
/// If an error occurs, all subsequent iterations return `None`.
///
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
/// use even if you do not statically know about all of the possible events.
pub fn find<Ev: Event>(&self) -> impl Iterator<Item = Result<Ev, BasicError>> + '_ {
self.iter_raw().filter_map(|ev| {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
@@ -269,67 +131,147 @@ impl<'a, T: Config, Evs: Decode> Events<T, Evs> {
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the first event found which decodes to the provided `Ev` type.
///
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
/// use even if you do not statically know about all of the possible events.
pub fn find_first<Ev: Event>(&self) -> Result<Option<Ev>, BasicError> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
/// Find an event that decodes to the type provided. Returns true if it was found.
///
/// **Note:** This method internally uses [`Events::iter_raw()`], so it is safe to
/// use even if you do not statically know about all of the possible events.
pub fn has<Ev: crate::Event>(&self) -> Result<bool, BasicError> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
/// A decoded event and associated details.
/// The event details.
#[derive(Debug, Clone, PartialEq)]
pub struct EventDetails<Evs> {
/// During which [`Phase`] was the event produced?
pub phase: Phase,
/// What index is this event in the stored events for this block.
pub index: u32,
/// The event itself.
pub event: Evs,
pub struct EventDetails {
phase: Phase,
index: u32,
pallet: String,
variant: String,
bytes: Vec<u8>,
// Dev note: this is here because we've pretty much had to generate it
// anyway, but expect it to be generated on the fly in future versions,
// and so don't expose it.
fields: Vec<(Option<String>, DecodedValue)>,
}
/// A Value which has been decoded from some raw bytes.
pub type DecodedValue = scale_value::Value<scale_value::scale::TypeId>;
/// The raw bytes for an event with associated details about
/// where and when it was emitted.
/// The raw data associated with some event.
#[derive(Debug, Clone, PartialEq)]
pub struct RawEventDetails {
pub struct EventDetailParts {
/// When was the event produced?
pub phase: Phase,
/// What index is this event in the stored events for this block.
pub index: u32,
/// The name of the pallet from whence the Event originated.
pub pallet: String,
/// The index of the pallet from whence the Event originated.
pub pallet_index: u8,
/// The name of the pallet Event variant.
/// The name of the pallet's Event variant.
pub variant: String,
/// The index of the pallet Event variant.
pub variant_index: u8,
/// The bytes representing the fields contained within the event.
/// All of the bytes representing this event, including the pallet
/// and variant index that the event originated from.
pub bytes: Vec<u8>,
/// Generic values representing each field of the event.
pub fields: Vec<DecodedValue>,
}
impl RawEventDetails {
/// Attempt to decode this [`RawEventDetails`] into a specific event.
pub fn as_event<E: Event>(&self) -> Result<Option<E>, CodecError> {
impl EventDetails {
/// Return the raw data associated with this event. Useful if you want
/// ownership over parts of the event data.
pub fn parts(self) -> EventDetailParts {
EventDetailParts {
phase: self.phase,
index: self.index,
pallet: self.pallet,
variant: self.variant,
bytes: self.bytes,
}
}
/// When was the event produced?
pub fn phase(&self) -> Phase {
self.phase
}
/// What index is this event in the stored events for this block.
pub fn index(&self) -> u32 {
self.index
}
/// The index of the pallet that the event originated from.
pub fn pallet_index(&self) -> u8 {
// Note: never panics because we set the first two bytes
// in `decode_event_details` to build this.
self.bytes[0]
}
/// The index of the event variant that the event originated from.
pub fn variant_index(&self) -> u8 {
// Note: never panics because we set the first two bytes
// in `decode_event_details` to build this.
self.bytes[1]
}
/// The name of the pallet from whence the Event originated.
pub fn pallet_name(&self) -> &str {
&self.pallet
}
/// The name of the pallet's Event variant.
pub fn variant_name(&self) -> &str {
&self.variant
}
/// Return the bytes representing this event, which include the pallet
/// and variant index that the event originated from.
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Return the bytes representing the fields stored in this event.
pub fn field_bytes(&self) -> &[u8] {
&self.bytes[2..]
}
/// Decode and provide the event fields back in the form of a composite
/// type, which represents either the named or unnamed fields that were
/// present.
// Dev note: if we can optimise Value decoding to avoid allocating
// while working through events, or if the event structure changes
// to allow us to skip over them, we'll no longer keep a copy of the
// decoded events in the event, and the actual decoding will happen
// when this method is called. This is why we return an owned vec and
// not a reference.
pub fn field_values(&self) -> scale_value::Composite<scale_value::scale::TypeId> {
if self.fields.is_empty() {
scale_value::Composite::Unnamed(vec![])
} else if self.fields[0].0.is_some() {
let named = self
.fields
.iter()
.map(|(n, f)| (n.clone().unwrap_or_default(), f.clone()))
.collect();
scale_value::Composite::Named(named)
} else {
let unnamed = self.fields.iter().map(|(_n, f)| f.clone()).collect();
scale_value::Composite::Unnamed(unnamed)
}
}
/// Attempt to decode these [`EventDetails`] into a specific static event.
/// This targets the fields within the event directly. You can also attempt to
/// decode the entirety of the event type (including the pallet and event
/// variants) using [`EventDetails::as_root_event()`].
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, CodecError> {
if self.pallet == E::PALLET && self.variant == E::EVENT {
Ok(Some(E::decode(&mut &self.bytes[..])?))
Ok(Some(E::decode(&mut &self.bytes[2..])?))
} else {
Ok(None)
}
}
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
/// the pallet and event enum variants as well as the event fields). A compatible
/// type for this is exposed via static codegen as a root level `Event` type.
pub fn as_root_event<E: Decode>(&self) -> Result<E, CodecError> {
E::decode(&mut &self.bytes[..])
}
}
// Attempt to dynamically decode a single event from our events input.
@@ -337,7 +279,7 @@ fn decode_raw_event_details<T: Config>(
metadata: &Metadata,
index: u32,
input: &mut &[u8],
) -> Result<RawEventDetails, BasicError> {
) -> Result<EventDetails, Error> {
// Decode basic event details:
let phase = Phase::decode(input)?;
let pallet_index = input.read_byte()?;
@@ -358,19 +300,20 @@ fn decode_raw_event_details<T: Config>(
event_metadata.event()
);
// Use metadata to figure out which bytes belong to this event:
let mut event_bytes = Vec::new();
// Use metadata to figure out which bytes belong to this event.
// the event bytes also include the pallet/variant index so that, if we
// like, we can decode them quite easily into a top level event type.
let mut event_bytes = vec![pallet_index, variant_index];
let mut event_fields = Vec::new();
for arg in event_metadata.variant().fields() {
let type_id = arg.ty().id();
for (name, type_id) in event_metadata.fields() {
let all_bytes = *input;
// consume some bytes for each event field, moving the cursor forward:
let value = scale_value::scale::decode_as_type(
input,
type_id,
*type_id,
&metadata.runtime_metadata().types,
)?;
event_fields.push(value);
event_fields.push((name.clone(), value));
// count how many bytes were consumed based on remaining length:
let consumed_len = all_bytes.len() - input.len();
// move those consumed bytes to the output vec unaltered:
@@ -382,12 +325,10 @@ fn decode_raw_event_details<T: Config>(
let topics = Vec::<T::Hash>::decode(input)?;
tracing::debug!("topics: {:?}", topics);
Ok(RawEventDetails {
Ok(EventDetails {
phase,
index,
pallet_index,
pallet: event_metadata.pallet().to_string(),
variant_index,
variant: event_metadata.event().to_string(),
bytes: event_bytes,
fields: event_fields,
@@ -400,8 +341,7 @@ pub(crate) mod test_utils {
use super::*;
use crate::{
Config,
DefaultConfig,
Phase,
SubstrateConfig,
};
use codec::Encode;
use frame_metadata::{
@@ -431,7 +371,7 @@ pub(crate) mod test_utils {
pub struct EventRecord<E: Encode> {
phase: Phase,
event: AllEvents<E>,
topics: Vec<<DefaultConfig as Config>::Hash>,
topics: Vec<<SubstrateConfig as Config>::Hash>,
}
/// Build an EventRecord, which encoded events in the format expected
@@ -474,9 +414,9 @@ pub(crate) mod test_utils {
/// Build an `Events` object for test purposes, based on the details provided,
/// and with a default block hash.
pub fn events<E: Decode + Encode>(
metadata: Arc<RwLock<Metadata>>,
metadata: Metadata,
event_records: Vec<EventRecord<E>>,
) -> Events<DefaultConfig, AllEvents<E>> {
) -> Events<SubstrateConfig> {
let num_events = event_records.len() as u32;
let mut event_bytes = Vec::new();
for ev in event_records {
@@ -487,17 +427,16 @@ pub(crate) mod test_utils {
/// Much like [`events`], but takes pre-encoded events and event count, so that we can
/// mess with the bytes in tests if we need to.
pub fn events_raw<E: Decode + Encode>(
metadata: Arc<RwLock<Metadata>>,
pub fn events_raw(
metadata: Metadata,
event_bytes: Vec<u8>,
num_events: u32,
) -> Events<DefaultConfig, AllEvents<E>> {
) -> Events<SubstrateConfig> {
Events {
block_hash: <DefaultConfig as Config>::Hash::default(),
event_bytes,
block_hash: <SubstrateConfig as Config>::Hash::default(),
event_bytes: event_bytes.into(),
metadata,
num_events,
_event_type: std::marker::PhantomData,
}
}
}
@@ -509,18 +448,16 @@ mod tests {
event_record,
events,
events_raw,
AllEvents,
},
*,
};
use crate::Phase;
use codec::Encode;
use scale_info::TypeInfo;
use scale_value::Value;
/// Build a fake wrapped metadata.
fn metadata<E: TypeInfo + 'static>() -> Arc<RwLock<Metadata>> {
Arc::new(RwLock::new(test_utils::metadata::<E>()))
fn metadata<E: TypeInfo + 'static>() -> Metadata {
test_utils::metadata::<E>()
}
/// [`RawEventDetails`] can be annoying to test, because it contains
@@ -542,170 +479,42 @@ mod tests {
pub fn assert_raw_events_match(
// Just for convenience, pass in the metadata type constructed
// by the `metadata` function above to simplify caller code.
metadata: &Arc<RwLock<Metadata>>,
actual: RawEventDetails,
metadata: &Metadata,
actual: EventDetails,
expected: TestRawEventDetails,
) {
let metadata = metadata.read();
let types = &metadata.runtime_metadata().types;
// Make sure that the bytes handed back line up with the fields handed back;
// encode the fields back into bytes and they should be equal.
let mut actual_bytes = vec![];
for field in &actual.fields {
for (_name, field) in &actual.fields {
scale_value::scale::encode_as_type(
field.clone(),
field,
field.context,
types,
&mut actual_bytes,
)
.expect("should be able to encode properly");
}
assert_eq!(actual_bytes, actual.bytes);
assert_eq!(actual_bytes, actual.field_bytes());
let actual_fields_no_context: Vec<_> = actual
.fields
.into_iter()
.map(|f| f.remove_context())
.field_values()
.into_values()
.map(|value| value.remove_context())
.collect();
// Check each of the other fields:
assert_eq!(actual.phase, expected.phase);
assert_eq!(actual.index, expected.index);
assert_eq!(actual.pallet, expected.pallet);
assert_eq!(actual.pallet_index, expected.pallet_index);
assert_eq!(actual.variant, expected.variant);
assert_eq!(actual.variant_index, expected.variant_index);
assert_eq!(actual.phase(), expected.phase);
assert_eq!(actual.index(), expected.index);
assert_eq!(actual.pallet_name(), expected.pallet);
assert_eq!(actual.pallet_index(), expected.pallet_index);
assert_eq!(actual.variant_name(), expected.variant);
assert_eq!(actual.variant_index(), expected.variant_index);
assert_eq!(actual_fields_no_context, expected.fields);
}
#[test]
fn statically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construct an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![event_record(Phase::Finalization, Event::A(1))],
);
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(1))
}]
);
}
#[test]
fn statically_decode_multiple_events() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events::<Event>(
metadata,
vec![
event_record(Phase::Initialization, Event::A(1)),
event_record(Phase::ApplyExtrinsic(123), Event::B(true)),
event_record(Phase::Finalization, Event::A(234)),
],
);
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![
EventDetails {
index: 0,
phase: Phase::Initialization,
event: AllEvents::Test(Event::A(1))
},
EventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
event: AllEvents::Test(Event::B(true))
},
EventDetails {
index: 2,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(234))
},
]
);
}
#[test]
fn statically_decode_multiple_events_until_error() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
enum Event {
A(u8),
B(bool),
}
// Create fake metadata that knows about our single event, above:
let metadata = metadata::<Event>();
// Encode 2 events:
let mut event_bytes = vec![];
event_record(Phase::Initialization, Event::A(1)).encode_to(&mut event_bytes);
event_record(Phase::ApplyExtrinsic(123), Event::B(true))
.encode_to(&mut event_bytes);
// Push a few naff bytes to the end (a broken third event):
event_bytes.extend_from_slice(&[3, 127, 45, 0, 2]);
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events_raw::<Event>(
metadata,
event_bytes,
3, // 2 "good" events, and then it'll hit the naff bytes.
);
let mut events_iter = events.iter();
assert_eq!(
events_iter.next().unwrap().unwrap(),
EventDetails {
index: 0,
phase: Phase::Initialization,
event: AllEvents::Test(Event::A(1))
}
);
assert_eq!(
events_iter.next().unwrap().unwrap(),
EventDetails {
index: 1,
phase: Phase::ApplyExtrinsic(123),
event: AllEvents::Test(Event::B(true))
}
);
// We'll hit an error trying to decode the third event:
assert!(events_iter.next().unwrap().is_err());
// ... and then "None" from then on.
assert!(events_iter.next().is_none());
assert!(events_iter.next().is_none());
}
#[test]
fn dynamically_decode_single_event() {
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
@@ -724,7 +533,7 @@ mod tests {
vec![event_record(Phase::ApplyExtrinsic(123), event)],
);
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
@@ -736,7 +545,7 @@ mod tests {
variant: "A".to_string(),
variant_index: 0,
fields: vec![
Value::uint(1u8),
Value::u128(1),
Value::bool(true),
Value::unnamed_composite(vec![Value::string("Hi")]),
],
@@ -771,7 +580,7 @@ mod tests {
],
);
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
@@ -783,7 +592,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(1u8)],
fields: vec![Value::u128(1)],
},
);
assert_raw_events_match(
@@ -809,7 +618,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(234u8)],
fields: vec![Value::u128(234)],
},
);
assert!(event_details.next().is_none());
@@ -837,13 +646,13 @@ mod tests {
// Encode our events in the format we expect back from a node, and
// construst an Events object to iterate them:
let events = events_raw::<Event>(
let events = events_raw(
metadata.clone(),
event_bytes,
3, // 2 "good" events, and then it'll hit the naff bytes.
);
let mut events_iter = events.iter_raw();
let mut events_iter = events.iter();
assert_raw_events_match(
&metadata,
events_iter.next().unwrap().unwrap(),
@@ -854,7 +663,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(1u8)],
fields: vec![Value::u128(1)],
},
);
assert_raw_events_match(
@@ -895,20 +704,8 @@ mod tests {
vec![event_record(Phase::Finalization, Event::A(1))],
);
// Statically decode:
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(1))
}]
);
// Dynamically decode:
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
@@ -919,7 +716,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::uint(1u8)],
fields: vec![Value::u128(1)],
},
);
assert!(event_details.next().is_none());
@@ -948,20 +745,8 @@ mod tests {
)],
);
// Statically decode:
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(CompactWrapper(1)))
}]
);
// Dynamically decode:
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
@@ -972,7 +757,7 @@ mod tests {
pallet_index: 0,
variant: "A".to_string(),
variant_index: 0,
fields: vec![Value::unnamed_composite(vec![Value::uint(1u8)])],
fields: vec![Value::unnamed_composite(vec![Value::u128(1)])],
},
);
assert!(event_details.next().is_none());
@@ -1002,20 +787,8 @@ mod tests {
vec![event_record(Phase::Finalization, Event::A(MyType::B))],
);
// Statically decode:
let event_details: Vec<EventDetails<AllEvents<Event>>> =
events.iter().collect::<Result<_, _>>().unwrap();
assert_eq!(
event_details,
vec![EventDetails {
index: 0,
phase: Phase::Finalization,
event: AllEvents::Test(Event::A(MyType::B))
}]
);
// Dynamically decode:
let mut event_details = events.iter_raw();
let mut event_details = events.iter();
assert_raw_events_match(
&metadata,
event_details.next().unwrap().unwrap(),
+55 -61
View File
@@ -4,14 +4,15 @@
//! Filtering individual events from subscriptions.
use super::Events;
use crate::{
BasicError,
Config,
Event,
use super::{
Events,
Phase,
StaticEvent,
};
use crate::{
Config,
Error,
};
use codec::Decode;
use futures::{
Stream,
StreamExt,
@@ -28,7 +29,7 @@ use std::{
/// exactly one of these will be `Some(event)` each iteration.
pub struct FilterEvents<'a, Sub: 'a, T: Config, Filter: EventFilter> {
// A subscription; in order for the Stream impl to apply, this will
// impl `Stream<Item = Result<Events<'a, T, Evs>, BasicError>> + Unpin + 'a`.
// impl `Stream<Item = Result<Events<'a, T, Evs>, Error>> + Unpin + 'a`.
sub: Sub,
// Each time we get Events from our subscription, they are stored here
// and iterated through in future stream iterations until exhausted.
@@ -37,7 +38,7 @@ pub struct FilterEvents<'a, Sub: 'a, T: Config, Filter: EventFilter> {
dyn Iterator<
Item = Result<
FilteredEventDetails<T::Hash, Filter::ReturnType>,
BasicError,
Error,
>,
> + Send
+ 'a,
@@ -56,14 +57,13 @@ impl<'a, Sub: 'a, T: Config, Filter: EventFilter> FilterEvents<'a, Sub, T, Filte
}
}
impl<'a, Sub, T, Evs, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
impl<'a, Sub, T, Filter> Stream for FilterEvents<'a, Sub, T, Filter>
where
Sub: Stream<Item = Result<Events<T, Evs>, BasicError>> + Unpin + 'a,
Sub: Stream<Item = Result<Events<T>, Error>> + Unpin + 'a,
T: Config,
Evs: Decode + 'static,
Filter: EventFilter,
{
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, BasicError>;
type Item = Result<FilteredEventDetails<T::Hash, Filter::ReturnType>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
@@ -112,14 +112,11 @@ pub trait EventFilter: private::Sealed {
/// The type we'll be handed back from filtering.
type ReturnType;
/// Filter the events based on the type implementing this trait.
fn filter<'a, T: Config, Evs: Decode + 'static>(
events: Events<T, Evs>,
fn filter<'a, T: Config>(
events: Events<T>,
) -> Box<
dyn Iterator<
Item = Result<
FilteredEventDetails<T::Hash, Self::ReturnType>,
BasicError,
>,
Item = Result<FilteredEventDetails<T::Hash, Self::ReturnType>, Error>,
> + Send
+ 'a,
>;
@@ -134,18 +131,16 @@ pub(crate) mod private {
// A special case impl for searching for a tuple of exactly one event (in this case, we don't
// need to return an `(Option<Event>,)`; we can just return `Event`.
impl<Ev: Event> private::Sealed for (Ev,) {}
impl<Ev: Event> EventFilter for (Ev,) {
impl<Ev: StaticEvent> private::Sealed for (Ev,) {}
impl<Ev: StaticEvent> EventFilter for (Ev,) {
type ReturnType = Ev;
fn filter<'a, T: Config, Evs: Decode + 'static>(
events: Events<T, Evs>,
fn filter<'a, T: Config>(
events: Events<T>,
) -> Box<
dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, BasicError>>
+ Send
+ 'a,
dyn Iterator<Item = Result<FilteredEventDetails<T::Hash, Ev>, Error>> + Send + 'a,
> {
let block_hash = events.block_hash();
let mut iter = events.into_iter_raw();
let mut iter = events.iter();
Box::new(std::iter::from_fn(move || {
for ev in iter.by_ref() {
// Forward any error immediately:
@@ -158,7 +153,7 @@ impl<Ev: Event> EventFilter for (Ev,) {
if let Ok(Some(event)) = ev {
// We found a match; return our tuple.
return Some(Ok(FilteredEventDetails {
phase: raw_event.phase,
phase: raw_event.phase(),
block_hash,
event,
}))
@@ -176,14 +171,14 @@ impl<Ev: Event> EventFilter for (Ev,) {
// A generalised impl for tuples of sizes greater than 1:
macro_rules! impl_event_filter {
($($ty:ident $idx:tt),+) => {
impl <$($ty: Event),+> private::Sealed for ( $($ty,)+ ) {}
impl <$($ty: Event),+> EventFilter for ( $($ty,)+ ) {
impl <$($ty: StaticEvent),+> private::Sealed for ( $($ty,)+ ) {}
impl <$($ty: StaticEvent),+> EventFilter for ( $($ty,)+ ) {
type ReturnType = ( $(Option<$ty>,)+ );
fn filter<'a, T: Config, Evs: Decode + 'static>(
events: Events<T, Evs>
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, BasicError>> + Send + 'a> {
fn filter<'a, T: Config>(
events: Events<T>
) -> Box<dyn Iterator<Item=Result<FilteredEventDetails<T::Hash,Self::ReturnType>, Error>> + Send + 'a> {
let block_hash = events.block_hash();
let mut iter = events.into_iter_raw();
let mut iter = events.iter();
Box::new(std::iter::from_fn(move || {
let mut out: ( $(Option<$ty>,)+ ) = Default::default();
for ev in iter.by_ref() {
@@ -199,7 +194,7 @@ macro_rules! impl_event_filter {
// We found a match; return our tuple.
out.$idx = Some(ev);
return Some(Ok(FilteredEventDetails {
phase: raw_event.phase,
phase: raw_event.phase(),
block_hash,
event: out
}))
@@ -232,24 +227,24 @@ mod test {
event_record,
events,
metadata,
AllEvents,
},
*,
};
use crate::{
Config,
DefaultConfig,
Metadata,
SubstrateConfig,
};
use codec::{
Decode,
Encode,
};
use codec::Encode;
use futures::{
stream,
Stream,
StreamExt,
};
use parking_lot::RwLock;
use scale_info::TypeInfo;
use std::sync::Arc;
// Some pretend events in a pallet
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
@@ -262,7 +257,7 @@ mod test {
// An event in our pallet that we can filter on.
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
struct EventA(u8);
impl crate::Event for EventA {
impl StaticEvent for EventA {
const PALLET: &'static str = "Test";
const EVENT: &'static str = "A";
}
@@ -270,7 +265,7 @@ mod test {
// An event in our pallet that we can filter on.
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
struct EventB(bool);
impl crate::Event for EventB {
impl StaticEvent for EventB {
const PALLET: &'static str = "Test";
const EVENT: &'static str = "B";
}
@@ -278,16 +273,15 @@ mod test {
// An event in our pallet that we can filter on.
#[derive(Clone, Debug, PartialEq, Decode, Encode, TypeInfo)]
struct EventC(u8, bool);
impl crate::Event for EventC {
impl StaticEvent for EventC {
const PALLET: &'static str = "Test";
const EVENT: &'static str = "C";
}
// A stream of fake events for us to try filtering on.
fn events_stream(
metadata: Arc<RwLock<Metadata>>,
) -> impl Stream<Item = Result<Events<DefaultConfig, AllEvents<PalletEvents>>, BasicError>>
{
metadata: Metadata,
) -> impl Stream<Item = Result<Events<SubstrateConfig>, Error>> {
stream::iter(vec![
events::<PalletEvents>(
metadata.clone(),
@@ -312,16 +306,16 @@ mod test {
],
),
])
.map(Ok::<_, BasicError>)
.map(Ok::<_, Error>)
}
#[tokio::test]
async fn filter_one_event_from_stream() {
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
let metadata = metadata::<PalletEvents>();
// Filter out fake event stream to select events matching `EventA` only.
let actual: Vec<_> =
FilterEvents::<_, DefaultConfig, (EventA,)>::new(events_stream(metadata))
FilterEvents::<_, SubstrateConfig, (EventA,)>::new(events_stream(metadata))
.map(|e| e.unwrap())
.collect()
.await;
@@ -329,17 +323,17 @@ mod test {
let expected = vec![
FilteredEventDetails {
phase: Phase::Initialization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: EventA(1),
},
FilteredEventDetails {
phase: Phase::Finalization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: EventA(2),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(3),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: EventA(3),
},
];
@@ -349,10 +343,10 @@ mod test {
#[tokio::test]
async fn filter_some_events_from_stream() {
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
let metadata = metadata::<PalletEvents>();
// Filter out fake event stream to select events matching `EventA` or `EventB`.
let actual: Vec<_> = FilterEvents::<_, DefaultConfig, (EventA, EventB)>::new(
let actual: Vec<_> = FilterEvents::<_, SubstrateConfig, (EventA, EventB)>::new(
events_stream(metadata),
)
.map(|e| e.unwrap())
@@ -362,32 +356,32 @@ mod test {
let expected = vec![
FilteredEventDetails {
phase: Phase::Initialization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (Some(EventA(1)), None),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(0),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (None, Some(EventB(true))),
},
FilteredEventDetails {
phase: Phase::Finalization,
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (Some(EventA(2)), None),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(1),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (None, Some(EventB(false))),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(2),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (None, Some(EventB(true))),
},
FilteredEventDetails {
phase: Phase::ApplyExtrinsic(3),
block_hash: <DefaultConfig as Config>::Hash::default(),
block_hash: <SubstrateConfig as Config>::Hash::default(),
event: (Some(EventA(3)), None),
},
];
@@ -397,11 +391,11 @@ mod test {
#[tokio::test]
async fn filter_no_events_from_stream() {
let metadata = Arc::new(RwLock::new(metadata::<PalletEvents>()));
let metadata = metadata::<PalletEvents>();
// Filter out fake event stream to select events matching `EventC` (none exist).
let actual: Vec<_> =
FilterEvents::<_, DefaultConfig, (EventC,)>::new(events_stream(metadata))
FilterEvents::<_, SubstrateConfig, (EventC,)>::new(events_stream(metadata))
.map(|e| e.unwrap())
.collect()
.await;
+43 -86
View File
@@ -2,108 +2,65 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module exposes the ability to work with events generated by a given block.
//! Subxt can either attempt to statically decode events into known types, or it
//! can hand back details of the raw event without knowing what shape its contents
//! are (this may be useful if we don't know what exactly we're looking for).
//!
//! This module is wrapped by the generated API in `RuntimeAPI::EventsApi`.
//!
//! # Examples
//!
//! ## Subscribe to all events
//!
//! Users can subscribe to all emitted events from blocks using `subscribe()`.
//!
//! To subscribe to all events from just the finalized blocks use `subscribe_finalized()`.
//!
//! To obtain the events from a given block use `at()`.
//!
//! ```no_run
//! # use futures::StreamExt;
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! # pub mod polkadot {}
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! let mut events = api.events().subscribe().await.unwrap();
//!
//! while let Some(ev) = events.next().await {
//! // Obtain all events from this block.
//! let ev: subxt::Events<_, _> = ev.unwrap();
//! // Print block hash.
//! println!("Event at block hash {:?}", ev.block_hash());
//! // Iterate over all events.
//! let mut iter = ev.iter();
//! while let Some(event_details) = iter.next() {
//! println!("Event details {:?}", event_details);
//! }
//! }
//! # }
//! ```
//!
//! ## Filter events
//!
//! The subxt exposes the ability to filter events via the `filter_events()` function.
//!
//! The function filters events from the provided tuple. If 1-tuple is provided, the events are
//! returned directly. Otherwise, we'll be given a corresponding tuple of `Option`'s, with exactly
//! one variant populated each time.
//!
//! ```no_run
//! # use futures::StreamExt;
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//!
//! let mut transfer_events = api
//! .events()
//! .subscribe()
//! .await
//! .unwrap()
//! .filter_events::<(polkadot::balances::events::Transfer,)>();
//!
//! while let Some(transfer_event) = transfer_events.next().await {
//! println!("Balance transfer event: {transfer_event:?}");
//! }
//! # }
//! ```
//! This module exposes the types and such necessary for working with events.
//! The two main entry points into events are [`crate::OnlineClient::events()`]
//! and calls like [crate::tx::TxProgress::wait_for_finalized_success()].
mod event_subscription;
mod events_client;
mod events_type;
mod filter_events;
pub use event_subscription::{
subscribe,
subscribe_finalized,
subscribe_to_block_headers_filling_in_gaps,
EventSub,
EventSubscription,
FinalizedEventSub,
};
pub use events_client::{
// Exposed only for testing:
subscribe_to_block_headers_filling_in_gaps,
EventsClient,
};
pub use events_type::{
at,
DecodedValue,
EventDetails,
Events,
RawEventDetails,
};
pub use filter_events::{
EventFilter,
FilterEvents,
FilteredEventDetails,
};
use codec::{
Decode,
Encode,
};
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
/// Generated API structures that represent an event implement this trait.
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait StaticEvent: Decode {
/// Pallet name.
const PALLET: &'static str;
/// Event name.
const EVENT: &'static str;
/// Returns true if the given pallet and event names match this event.
fn is_event(pallet: &str, event: &str) -> bool {
Self::PALLET == pallet && Self::EVENT == event
}
}
/// A phase of a block's execution.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Decode, Encode)]
pub enum Phase {
/// Applying an extrinsic.
ApplyExtrinsic(u32),
/// Finalizing the block.
Finalization,
/// Initializing the block.
Initialization,
}
+75 -338
View File
@@ -10,164 +10,80 @@
//! - [Query constants](https://docs.substrate.io/how-to-guides/v3/basics/configurable-constants/) (Constants)
//! - [Subscribe to events](https://docs.substrate.io/v3/runtime/events-and-errors/) (Events)
//!
//! # Initializing the API client
//!
//! # Generate the runtime API
//! To interact with a node, you'll need to construct a client.
//!
//! Subxt generates a runtime API from downloaded static metadata. The metadata can be downloaded using the
//! ```no_run
//! use subxt::{OnlineClient, PolkadotConfig};
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//! # }
//! ```
//!
//! This default client connects to a locally running node, but can be configured to point anywhere.
//! Additionally, an [`crate::OfflineClient`] is available to perform operations that don't require a
//! network connection to a node.
//!
//! The client takes a type parameter, here [`crate::PolkadotConfig`], which bakes in assumptions about
//! the structure of extrinsics and the underlying types used by the node for things like block numbers.
//! If the node you'd like to interact with deviates from Polkadot or the default Substrate node in these
//! areas, you'll need to configure them by implementing the [`crate::config::Config`] type yourself.
//!
//! # Generating runtime types
//!
//! Subxt can generate types at compile time to help you interact with a node. The metadata can be downloaded using the
//! [subxt-cli](https://crates.io/crates/subxt-cli) tool.
//!
//! To generate the runtime API, use the `subxt` attribute which points at downloaded static metadata.
//! To generate the types, use the `subxt` attribute which points at downloaded static metadata.
//!
//! ```ignore
//! #[subxt::subxt(runtime_metadata_path = "metadata.scale")]
//! pub mod node_runtime { }
//! ```
//!
//! The `node_runtime` has the following hierarchy:
//! For more information, please visit the [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/)
//! documentation.
//!
//! ```rust
//! pub mod node_runtime {
//! pub mod PalletName {
//! pub mod calls { }
//! pub mod storage { }
//! pub mod constants { }
//! pub mod events { }
//! }
//! }
//! ```
//! # Interacting with the API
//!
//! For more information regarding the `node_runtime` hierarchy, please visit the
//! [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/) documentation.
//! Once instantiated, a client, exposes four functions:
//! - `.tx()` for submitting extrinsics/transactions. See [`crate::tx::TxClient`] for more details, or see
//! the [balance_transfer](../examples/examples/balance_transfer.rs) example.
//! - `.storage()` for fetching and iterating over storage entries. See [`crate::storage::StorageClient`] for more details, or see
//! the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example.
//! - `.constants()` for getting hold of constants. See [`crate::constants::ConstantsClient`] for more details, or see
//! the [fetch_constants](../examples/examples/fetch_constants.rs) example.
//! - `.events()` for subscribing/obtaining events. See [`crate::events::EventsClient`] for more details, or see:
//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks.
//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event.
//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event.
//!
//! # Static Metadata Validation
//!
//! # Initializing the API client
//! If you use types generated by the [`crate::subxt`] macro, there is a chance that they will fall out of sync
//! with the actual state of the node you're trying to interact with.
//!
//! When you attempt to use any of these static types to interact with a node, Subxt will validate that they are
//! still compatible and issue an error if they have deviated.
//!
//! Additionally, you can validate that the entirety of the statically generated code aligns with a node like so:
//!
//! ```no_run
//! use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! use subxt::{OnlineClient, PolkadotConfig};
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! let api = ClientBuilder::new()
//! .set_url("wss://rpc.polkadot.io:443")
//! .build()
//! .await
//! .unwrap()
//! .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! # }
//! ```
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! The `RuntimeApi` type is generated by the `subxt` macro from the supplied metadata. This can be parameterized with user
//! supplied implementations for the `Config` and `Extra` types, if the default implementation differs from the target
//! chain.
//!
//! To ensure that extrinsics are properly submitted, during the build phase of the Client the
//! runtime metadata of the node is downloaded. If the URL is not specified (`set_url`), the local host is used instead.
//!
//!
//! # Submit Extrinsics
//!
//! Extrinsics are obtained using the API's `RuntimeApi::tx()` method, followed by `pallet_name()` and then the
//! `call_item_name()`.
//!
//! Submit an extrinsic, returning success once the transaction is validated and accepted into the pool:
//!
//! Please visit the [balance_transfer](../examples/examples/balance_transfer.rs) example for more details.
//!
//!
//! # Querying Storage
//!
//! The runtime storage is queried via the generated `RuntimeApi::storage()` method, followed by the `pallet_name()` and
//! then the `storage_item_name()`.
//!
//! Please visit the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example for more details.
//!
//! # Query Constants
//!
//! Constants are embedded into the node's metadata.
//!
//! The subxt offers the ability to query constants from the runtime metadata (metadata downloaded when constructing
//! the client, *not* the one provided for API generation).
//!
//! To query constants use the generated `RuntimeApi::constants()` method, followed by the `pallet_name()` and then the
//! `constant_item_name()`.
//!
//! Please visit the [fetch_constants](../examples/examples/fetch_constants.rs) example for more details.
//!
//! # Subscribe to Events
//!
//! To subscribe to events, use the generated `RuntimeApi::events()` method which exposes:
//! - `subscribe()` - Subscribe to events emitted from blocks. These blocks haven't necessarily been finalised.
//! - `subscribe_finalized()` - Subscribe to events from finalized blocks.
//! - `at()` - Obtain events at a given block hash.
//!
//!
//! *Examples*
//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks.
//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event.
//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event.
//!
//! # Static Metadata Validation
//!
//! There are two types of metadata that the subxt is aware of:
//! - static metadata: Metadata used for generating the API.
//! - runtime metadata: Metadata downloaded from the target node when a subxt client is created.
//!
//! There are cases when the static metadata is different from the runtime metadata of a node.
//! Such is the case when the node performs a runtime update.
//!
//! To ensure that subxt can properly communicate with the target node the static metadata is validated
//! against the runtime metadata of the node.
//!
//! This validation is performed at the Call, Constant, and Storage levels, as well for the entire metadata.
//! The level of granularity ensures that the users can still submit a given call, even if another
//! call suffered changes.
//!
//! Full metadata validation:
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! # pub mod polkadot {}
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // To make sure that all of our statically generated pallets are compatible with the
//! // runtime node, we can run this check:
//! api.validate_metadata().unwrap();
//! # }
//! ```
//!
//! Call level validation:
//!
//! ```ignore
//! # use sp_keyring::AccountKeyring;
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! # pub mod polkadot {}
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // Submit the `transfer` extrinsic from Alice's account to Bob's.
//! let dest = AccountKeyring::Bob.to_account_id().into();
//!
//! let extrinsic = api
//! .tx()
//! .balances()
//! // Constructing an extrinsic will fail if the metadata
//! // is not in sync with the generated API.
//! .transfer(dest, 123_456_789_012_345)
//! .unwrap();
//! if let Err(_e) = polkadot::validate_codegen(&api) {
//! println!("Generated code is not up to date with node we're connected to");
//! }
//! # }
//! ```
//!
@@ -204,221 +120,42 @@
)]
#![allow(clippy::type_complexity)]
pub use frame_metadata::StorageHasher;
pub use subxt_macro::subxt;
pub use bitvec;
pub use codec;
pub use sp_core;
pub use sp_runtime;
use codec::{
Decode,
DecodeAll,
Encode,
};
use core::fmt::Debug;
use derivative::Derivative;
mod client;
mod config;
mod error;
pub mod client;
pub mod config;
pub mod constants;
pub mod dynamic;
pub mod error;
pub mod events;
pub mod extrinsic;
mod metadata;
pub mod metadata;
pub mod rpc;
pub mod storage;
mod transaction;
pub mod updates;
pub mod tx;
pub mod utils;
// Expose a few of the most common types at root,
// but leave most types behind their respoctive modules.
pub use crate::{
client::{
Client,
ClientBuilder,
SubmittableExtrinsic,
OfflineClient,
OnlineClient,
},
config::{
Config,
DefaultConfig,
},
error::{
BasicError,
Error,
GenericError,
HasModuleError,
ModuleError,
ModuleErrorData,
RuntimeError,
TransactionError,
},
events::{
EventDetails,
Events,
RawEventDetails,
},
extrinsic::{
PairSigner,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder,
SubstrateExtrinsicParams,
SubstrateExtrinsicParamsBuilder,
},
metadata::{
ErrorMetadata,
Metadata,
MetadataError,
PalletMetadata,
},
rpc::{
BlockNumber,
ReadProof,
RpcClient,
SystemProperties,
},
storage::{
KeyIter,
StorageEntry,
StorageEntryKey,
StorageMapKey,
},
transaction::{
TransactionEvents,
TransactionInBlock,
TransactionProgress,
TransactionStatus,
PolkadotConfig,
SubstrateConfig,
},
error::Error,
metadata::Metadata,
};
/// Trait to uniquely identify the call (extrinsic)'s identity from the runtime metadata.
///
/// Generated API structures that represent each of the different possible
/// calls to a node each implement this trait.
///
/// When encoding an extrinsic, we use this information to know how to map
/// the call to the specific pallet and call index needed by a particular node.
pub trait Call: Encode {
/// Pallet name.
const PALLET: &'static str;
/// Function name.
const FUNCTION: &'static str;
/// Returns true if the given pallet and function names match this call.
fn is_call(pallet: &str, function: &str) -> bool {
Self::PALLET == pallet && Self::FUNCTION == function
}
/// Re-export external crates that are made use of in the subxt API.
pub mod ext {
pub use bitvec;
pub use codec;
pub use frame_metadata;
pub use scale_value;
pub use sp_core;
pub use sp_runtime;
}
/// Trait to uniquely identify the events's identity from the runtime metadata.
///
/// Generated API structures that represent an event implement this trait.
///
/// The trait is utilized to decode emitted events from a block, via obtaining the
/// form of the `Event` from the metadata.
pub trait Event: Decode {
/// Pallet name.
const PALLET: &'static str;
/// Event name.
const EVENT: &'static str;
/// Returns true if the given pallet and event names match this event.
fn is_event(pallet: &str, event: &str) -> bool {
Self::PALLET == pallet && Self::EVENT == event
}
}
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Encoded(pub Vec<u8>);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec<u8> {
self.0.to_owned()
}
}
/// A phase of a block's execution.
#[derive(Clone, Debug, Eq, PartialEq, Decode, Encode)]
pub enum Phase {
/// Applying an extrinsic.
ApplyExtrinsic(u32),
/// Finalizing the block.
Finalization,
/// Initializing the block.
Initialization,
}
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
///
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
/// access the real type `T` [`Self::try_decode`] needs to be used.
#[derive(Derivative, Encode, Decode)]
#[derivative(
Debug(bound = ""),
Clone(bound = ""),
PartialEq(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T: Decode> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T> {
T::decode_all(&mut &self.data[..]).ok()
}
/// Returns the length of the encoded `T`.
pub fn encoded_len(&self) -> usize {
self.data.len()
}
/// Returns the encoded data.
pub fn encoded(&self) -> &[u8] {
&self.data
}
/// Create from the given encoded `data`.
pub fn from_encoded(data: Vec<u8>) -> Self {
Self {
data,
_phantom: PhantomDataSendSync::new(),
}
}
}
/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).
#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
#[scale_info(skip_type_params(T))]
#[doc(hidden)]
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
impl<T> PhantomDataSendSync<T> {
pub(crate) fn new() -> Self {
Self(core::marker::PhantomData)
}
}
unsafe impl<T> Send for PhantomDataSendSync<T> {}
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// This represents a key-value collection and is SCALE compatible
/// with collections like BTreeMap. This has the same type params
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
@@ -0,0 +1,79 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::Metadata;
use crate::{
dynamic::DecodedValue,
error::Error,
};
use codec::Decode;
use frame_metadata::StorageEntryType;
/// This trait is implemented for types which can be decoded with the help of metadata.
pub trait DecodeWithMetadata {
/// The type that we'll get back from decoding.
type Target;
/// Given some metadata and a type ID, attempt to SCALE decode the provided bytes into `Self`.
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error>;
/// Decode a storage item using metadata. By default, this uses the metadata to
/// work out the type ID to use, but for static items we can short circuit this
/// lookup.
fn decode_storage_with_metadata(
bytes: &mut &[u8],
pallet_name: &str,
storage_entry: &str,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
let ty = &metadata.pallet(pallet_name)?.storage(storage_entry)?.ty;
let id = match ty {
StorageEntryType::Plain(ty) => ty.id(),
StorageEntryType::Map { value, .. } => value.id(),
};
Self::decode_with_metadata(bytes, id, metadata)
}
}
// Things can be dynamically decoded to our Value type:
impl DecodeWithMetadata for DecodedValue {
type Target = Self;
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self::Target, Error> {
let res = scale_value::scale::decode_as_type(bytes, type_id, metadata.types())?;
Ok(res)
}
}
/// Any type implementing [`Decode`] can also be decoded with the help of metadata.
pub struct DecodeStaticType<T>(std::marker::PhantomData<T>);
impl<T: Decode> DecodeWithMetadata for DecodeStaticType<T> {
type Target = T;
fn decode_with_metadata(
bytes: &mut &[u8],
_type_id: u32,
_metadata: &Metadata,
) -> Result<Self::Target, Error> {
T::decode(bytes).map_err(|e| e.into())
}
fn decode_storage_with_metadata(
bytes: &mut &[u8],
_pallet_name: &str,
_storage_entry: &str,
_metadata: &Metadata,
) -> Result<Self::Target, Error> {
T::decode(bytes).map_err(|e| e.into())
}
}
@@ -0,0 +1,67 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
dynamic::Value,
error::Error,
metadata::Metadata,
};
use codec::Encode;
/// This trait is implemented for types which can be encoded with the help of metadata.
pub trait EncodeWithMetadata {
/// SCALE encode this type to bytes, possibly with the help of metadata.
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error>;
}
impl EncodeWithMetadata for Value<()> {
fn encode_with_metadata(
&self,
type_id: u32,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
scale_value::scale::encode_as_type(self, type_id, metadata.types(), bytes)
.map_err(|e| e.into())
}
}
/// Any type implementing [`Encode`] can also be encoded with the help of metadata.
pub struct EncodeStaticType<T>(pub T);
impl<T: Encode> EncodeWithMetadata for EncodeStaticType<T> {
fn encode_with_metadata(
&self,
_type_id: u32,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
self.0.encode_to(bytes);
Ok(())
}
}
// We can transparently Encode anything wrapped in EncodeStaticType, too.
impl<E: Encode> Encode for EncodeStaticType<E> {
fn size_hint(&self) -> usize {
self.0.size_hint()
}
fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
self.0.encode_to(dest)
}
fn encode(&self) -> Vec<u8> {
self.0.encode()
}
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.using_encoded(f)
}
fn encoded_size(&self) -> usize {
self.0.encoded_size()
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// Locate an item of a known type in the metadata.
/// We should already know that the item we're looking
/// for is a call or event for instance, and then with this,
/// we can dig up details for that item in the metadata.
pub trait MetadataLocation {
/// The pallet in which the item lives.
fn pallet(&self) -> &str;
/// The name of the item.
fn item(&self) -> &str;
}
+146 -133
View File
@@ -3,7 +3,6 @@
// see LICENSE for license details.
use super::hash_cache::HashCache;
use crate::Call;
use codec::Error as CodecError;
use frame_metadata::{
PalletConstantMetadata,
@@ -16,8 +15,8 @@ use frame_metadata::{
use parking_lot::RwLock;
use scale_info::{
form::PortableForm,
PortableRegistry,
Type,
Variant,
};
use std::{
collections::HashMap,
@@ -75,7 +74,11 @@ struct MetadataInner {
metadata: RuntimeMetadataV14,
pallets: HashMap<String, PalletMetadata>,
events: HashMap<(u8, u8), EventMetadata>,
// Errors are hashed by pallet index.
errors: HashMap<(u8, u8), ErrorMetadata>,
// Type of the DispatchError type, which is what comes back if
// an extrinsic fails.
dispatch_error_ty: Option<u32>,
// The hashes uniquely identify parts of the metadata; different
// hashes mean some type difference exists between static and runtime
// versions. We cache them here to avoid recalculating:
@@ -93,7 +96,7 @@ pub struct Metadata {
impl Metadata {
/// Returns a reference to [`PalletMetadata`].
pub fn pallet(&self, name: &'static str) -> Result<&PalletMetadata, MetadataError> {
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
self.inner
.pallets
.get(name)
@@ -128,6 +131,16 @@ impl Metadata {
Ok(error)
}
/// Return the DispatchError type ID if it exists.
pub fn dispatch_error_ty(&self) -> Option<u32> {
self.inner.dispatch_error_ty
}
/// Return the type registry embedded within the metadata.
pub fn types(&self) -> &PortableRegistry {
&self.inner.metadata.types
}
/// Resolve a type definition.
pub fn resolve_type(&self, id: u32) -> Option<&Type<PortableForm>> {
self.inner.metadata.types.resolve(id)
@@ -139,23 +152,25 @@ impl Metadata {
}
/// Obtain the unique hash for a specific storage entry.
pub fn storage_hash<S: crate::StorageEntry>(
pub fn storage_hash(
&self,
pallet: &str,
storage: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_storage_hashes
.get_or_insert(S::PALLET, S::STORAGE, || {
subxt_metadata::get_storage_hash(
&self.inner.metadata,
S::PALLET,
S::STORAGE,
)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
}
})
.get_or_insert(pallet, storage, || {
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => {
MetadataError::PalletNotFound
}
subxt_metadata::NotFound::Item => {
MetadataError::StorageNotFound
}
}
})
})
}
@@ -183,21 +198,23 @@ impl Metadata {
}
/// Obtain the unique hash for a call.
pub fn call_hash<C: crate::Call>(&self) -> Result<[u8; 32], MetadataError> {
pub fn call_hash(
&self,
pallet: &str,
function: &str,
) -> Result<[u8; 32], MetadataError> {
self.inner
.cached_call_hashes
.get_or_insert(C::PALLET, C::FUNCTION, || {
subxt_metadata::get_call_hash(
&self.inner.metadata,
C::PALLET,
C::FUNCTION,
)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
.get_or_insert(pallet, function, || {
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function)
.map_err(|e| {
match e {
subxt_metadata::NotFound::Pallet => {
MetadataError::PalletNotFound
}
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
}
})
})
}
@@ -222,7 +239,8 @@ impl Metadata {
pub struct PalletMetadata {
index: u8,
name: String,
calls: HashMap<String, u8>,
call_indexes: HashMap<String, u8>,
call_ty_id: Option<u32>,
storage: HashMap<String, StorageEntryMetadata<PortableForm>>,
constants: HashMap<String, PalletConstantMetadata<PortableForm>>,
}
@@ -238,15 +256,18 @@ impl PalletMetadata {
self.index
}
/// If calls exist for this pallet, this returns the type ID of the variant
/// representing the different possible calls.
pub fn call_ty_id(&self) -> Option<u32> {
self.call_ty_id
}
/// Attempt to resolve a call into an index in this pallet, failing
/// if the call is not found in this pallet.
pub fn call_index<C>(&self) -> Result<u8, MetadataError>
where
C: Call,
{
pub fn call_index(&self, function: &str) -> Result<u8, MetadataError> {
let fn_index = *self
.calls
.get(C::FUNCTION)
.call_indexes
.get(function)
.ok_or(MetadataError::CallNotFound)?;
Ok(fn_index)
}
@@ -273,9 +294,12 @@ impl PalletMetadata {
/// Metadata for specific events.
#[derive(Clone, Debug)]
pub struct EventMetadata {
pallet: String,
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
event: String,
variant: Variant<PortableForm>,
fields: Vec<(Option<String>, u32)>,
docs: Vec<String>,
}
impl EventMetadata {
@@ -289,21 +313,25 @@ impl EventMetadata {
&self.event
}
/// Get the type def variant for the pallet event.
pub fn variant(&self) -> &Variant<PortableForm> {
&self.variant
/// The names and types of each field in the event.
pub fn fields(&self) -> &[(Option<String>, u32)] {
&self.fields
}
/// Documentation for this event.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Metadata for specific errors obtained from the pallet's `PalletErrorMetadata`.
///
/// This holds in memory information regarding the Pallet's name, Error's name, and the underlying
/// metadata representation.
/// Details about a specific runtime error.
#[derive(Clone, Debug)]
pub struct ErrorMetadata {
pallet: String,
// The pallet name is shared across every event, so put it
// behind an Arc to avoid lots of needless clones of it existing.
pallet: Arc<str>,
error: String,
variant: Variant<PortableForm>,
docs: Vec<String>,
}
impl ErrorMetadata {
@@ -312,29 +340,31 @@ impl ErrorMetadata {
&self.pallet
}
/// Get the name of the specific pallet error.
/// The name of the error.
pub fn error(&self) -> &str {
&self.error
}
/// Get the description of the specific pallet error.
pub fn description(&self) -> &[String] {
self.variant.docs()
/// Documentation for the error.
pub fn docs(&self) -> &[String] {
&self.docs
}
}
/// Error originated from converting a runtime metadata [RuntimeMetadataPrefixed] to
/// the internal [Metadata] representation.
///
/// The runtime metadata is converted when building the [crate::client::Client].
#[derive(Debug, thiserror::Error)]
pub enum InvalidMetadataError {
/// Invalid prefix
#[error("Invalid prefix")]
InvalidPrefix,
/// Invalid version
#[error("Invalid version")]
InvalidVersion,
/// Type missing from type registry
#[error("Type {0} missing from type registry")]
MissingType(u32),
/// Type was not a variant/enum type
#[error("Type {0} was not a variant/enum type")]
TypeDefNotVariant(u32),
}
@@ -366,15 +396,18 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
.pallets
.iter()
.map(|pallet| {
let calls = pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| {
let type_def_variant = get_type_def_variant(call.ty.id())?;
let calls = type_def_variant
.variants()
.iter()
.map(|v| (v.name().clone(), v.index()))
.collect();
Ok(calls)
})?;
let call_ty_id = pallet.calls.as_ref().map(|c| c.ty.id());
let call_indexes =
pallet.calls.as_ref().map_or(Ok(HashMap::new()), |call| {
let type_def_variant = get_type_def_variant(call.ty.id())?;
let call_indexes = type_def_variant
.variants()
.iter()
.map(|v| (v.name().clone(), v.index()))
.collect();
Ok(call_indexes)
})?;
let storage = pallet.storage.as_ref().map_or(HashMap::new(), |storage| {
storage
@@ -393,7 +426,8 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
let pallet_metadata = PalletMetadata {
index: pallet.index,
name: pallet.name.to_string(),
calls,
call_indexes,
call_ty_id,
storage,
constants,
};
@@ -402,55 +436,54 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
})
.collect::<Result<_, _>>()?;
let pallet_events = metadata
.pallets
.iter()
.filter_map(|pallet| {
pallet.event.as_ref().map(|event| {
let type_def_variant = get_type_def_variant(event.ty.id())?;
Ok((pallet, type_def_variant))
})
})
.collect::<Result<Vec<_>, _>>()?;
let events = pallet_events
.iter()
.flat_map(|(pallet, type_def_variant)| {
type_def_variant.variants().iter().map(move |var| {
let key = (pallet.index, var.index());
let value = EventMetadata {
pallet: pallet.name.clone(),
event: var.name().clone(),
variant: var.clone(),
};
(key, value)
})
})
.collect();
let mut events = HashMap::<(u8, u8), EventMetadata>::new();
for pallet in &metadata.pallets {
if let Some(event) = &pallet.event {
let pallet_name: Arc<str> = pallet.name.to_string().into();
let event_type_id = event.ty.id();
let event_variant = get_type_def_variant(event_type_id)?;
for variant in event_variant.variants() {
events.insert(
(pallet.index, variant.index()),
EventMetadata {
pallet: pallet_name.clone(),
event: variant.name().to_owned(),
fields: variant
.fields()
.iter()
.map(|f| (f.name().map(|n| n.to_owned()), f.ty().id()))
.collect(),
docs: variant.docs().to_vec(),
},
);
}
}
}
let pallet_errors = metadata
.pallets
let mut errors = HashMap::<(u8, u8), ErrorMetadata>::new();
for pallet in &metadata.pallets {
if let Some(error) = &pallet.error {
let pallet_name: Arc<str> = pallet.name.to_string().into();
let error_variant = get_type_def_variant(error.ty.id())?;
for variant in error_variant.variants() {
errors.insert(
(pallet.index, variant.index()),
ErrorMetadata {
pallet: pallet_name.clone(),
error: variant.name().clone(),
docs: variant.docs().to_vec(),
},
);
}
}
}
let dispatch_error_ty = metadata
.types
.types()
.iter()
.filter_map(|pallet| {
pallet.error.as_ref().map(|error| {
let type_def_variant = get_type_def_variant(error.ty.id())?;
Ok((pallet, type_def_variant))
})
})
.collect::<Result<Vec<_>, _>>()?;
let errors = pallet_errors
.iter()
.flat_map(|(pallet, type_def_variant)| {
type_def_variant.variants().iter().map(move |var| {
let key = (pallet.index, var.index());
let value = ErrorMetadata {
pallet: pallet.name.clone(),
error: var.name().clone(),
variant: var.clone(),
};
(key, value)
})
})
.collect();
.find(|ty| ty.ty().path().segments() == ["sp_runtime", "DispatchError"])
.map(|ty| ty.id());
Ok(Metadata {
inner: Arc::new(MetadataInner {
@@ -458,6 +491,7 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
pallets,
events,
errors,
dispatch_error_ty,
cached_metadata_hash: Default::default(),
cached_call_hashes: Default::default(),
cached_constant_hashes: Default::default(),
@@ -470,7 +504,6 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
#[cfg(test)]
mod tests {
use super::*;
use crate::StorageEntryKey;
use frame_metadata::{
ExtrinsicMetadata,
PalletStorageMetadata,
@@ -553,14 +586,7 @@ mod tests {
fn metadata_call_inner_cache() {
let metadata = load_metadata();
#[derive(codec::Encode)]
struct ValidCall;
impl crate::Call for ValidCall {
const PALLET: &'static str = "System";
const FUNCTION: &'static str = "fill_block";
}
let hash = metadata.call_hash::<ValidCall>();
let hash = metadata.call_hash("System", "fill_block");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_call_hashes.get_or_insert(
@@ -601,20 +627,7 @@ mod tests {
#[test]
fn metadata_storage_inner_cache() {
let metadata = load_metadata();
#[derive(codec::Encode)]
struct ValidStorage;
impl crate::StorageEntry for ValidStorage {
const PALLET: &'static str = "System";
const STORAGE: &'static str = "Account";
type Value = ();
fn key(&self) -> StorageEntryKey {
unreachable!("Should not be called");
}
}
let hash = metadata.storage_hash::<ValidStorage>();
let hash = metadata.storage_hash("System", "Account");
let mut call_number = 0;
let hash_cached = metadata.inner.cached_storage_hashes.get_or_insert(
+17
View File
@@ -2,9 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types representing the metadata obtained from a node.
mod decode_with_metadata;
mod encode_with_metadata;
mod hash_cache;
mod metadata_location;
mod metadata_type;
pub use metadata_location::MetadataLocation;
pub use metadata_type::{
ErrorMetadata,
EventMetadata,
@@ -13,3 +20,13 @@ pub use metadata_type::{
MetadataError,
PalletMetadata,
};
pub use decode_with_metadata::{
DecodeStaticType,
DecodeWithMetadata,
};
pub use encode_with_metadata::{
EncodeStaticType,
EncodeWithMetadata,
};
+61 -95
View File
@@ -7,67 +7,29 @@
//! This is used behind the scenes by various `subxt` APIs, but can
//! also be used directly.
//!
//! # Examples
//! # Example
//!
//! ## Fetch Storage
//! Fetching storage keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageKeyPrefix;
//! # use subxt::rpc::Rpc;
//! use subxt::{ PolkadotConfig, OnlineClient, storage::StorageKey };
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! // Storage prefix is `twox_128("System") ++ twox_128("ExtrinsicCount")`.
//! let key = StorageKeyPrefix::new::<polkadot::system::storage::ExtrinsicCount>()
//! .to_storage_key();
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
//!
//! // Obtain the RPC from a generated API
//! let rpc: &Rpc<_> = api
//! .client
//! .rpc();
//!
//! let result = rpc.storage(&key, None).await.unwrap();
//! println!("Storage result: {:?}", result);
//! # }
//! ```
//!
//! ## Fetch Keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageKeyPrefix;
//! # use subxt::rpc::Rpc;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! let key = StorageKeyPrefix::new::<polkadot::xcm_pallet::storage::VersionNotifiers>()
//! .to_storage_key();
//!
//! // Obtain the RPC from a generated API
//! let rpc: &Rpc<_> = api
//! .client
//! .rpc();
//! let key = polkadot::storage()
//! .xcm_pallet()
//! .version_notifiers_root()
//! .to_bytes();
//!
//! // Fetch up to 10 keys.
//! let keys = rpc
//! .storage_keys_paged(Some(key), 10, None, None)
//! let keys = api
//! .rpc()
//! .storage_keys_paged(&key, 10, None, None)
//! .await
//! .unwrap();
//!
@@ -88,10 +50,10 @@ use std::{
};
use crate::{
error::BasicError,
error::Error,
utils::PhantomDataSendSync,
Config,
Metadata,
PhantomDataSendSync,
};
use codec::{
Decode,
@@ -198,7 +160,7 @@ pub type SystemProperties = serde_json::Map<String, serde_json::Value>;
/// must be kept compatible with that type from the target substrate version.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SubstrateTransactionStatus<Hash, BlockHash> {
pub enum SubstrateTxStatus<Hash, BlockHash> {
/// Transaction is part of the future queue.
Future,
/// Transaction is part of the ready queue.
@@ -324,13 +286,13 @@ impl<T: Config> Rpc<T> {
}
}
/// Fetch a storage key
/// Fetch the raw bytes for a given storage key
pub async fn storage(
&self,
key: &StorageKey,
key: &[u8],
hash: Option<T::Hash>,
) -> Result<Option<StorageData>, BasicError> {
let params = rpc_params![key, hash];
) -> Result<Option<StorageData>, Error> {
let params = rpc_params![to_hex(key), hash];
let data = self.client.request("state_getStorage", params).await?;
Ok(data)
}
@@ -340,12 +302,13 @@ impl<T: Config> Rpc<T> {
/// If `start_key` is passed, return next keys in storage in lexicographic order.
pub async fn storage_keys_paged(
&self,
key: Option<StorageKey>,
key: &[u8],
count: u32,
start_key: Option<StorageKey>,
start_key: Option<&[u8]>,
hash: Option<T::Hash>,
) -> Result<Vec<StorageKey>, BasicError> {
let params = rpc_params![key, count, start_key, hash];
) -> Result<Vec<StorageKey>, Error> {
let start_key = start_key.map(to_hex);
let params = rpc_params![to_hex(key), count, start_key, hash];
let data = self.client.request("state_getKeysPaged", params).await?;
Ok(data)
}
@@ -353,10 +316,11 @@ impl<T: Config> Rpc<T> {
/// Query historical storage entries
pub async fn query_storage(
&self,
keys: Vec<StorageKey>,
keys: impl IntoIterator<Item = &[u8]>,
from: T::Hash,
to: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
) -> Result<Vec<StorageChangeSet<T::Hash>>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, from, to];
self.client
.request("state_queryStorage", params)
@@ -367,9 +331,10 @@ impl<T: Config> Rpc<T> {
/// Query historical storage entries
pub async fn query_storage_at(
&self,
keys: &[StorageKey],
keys: impl IntoIterator<Item = &[u8]>,
at: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
) -> Result<Vec<StorageChangeSet<T::Hash>>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, at];
self.client
.request("state_queryStorageAt", params)
@@ -378,7 +343,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch the genesis hash
pub async fn genesis_hash(&self) -> Result<T::Hash, BasicError> {
pub async fn genesis_hash(&self) -> Result<T::Hash, Error> {
let block_zero = 0u32;
let params = rpc_params![block_zero];
let genesis_hash: Option<T::Hash> =
@@ -387,7 +352,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch the metadata
pub async fn metadata(&self) -> Result<Metadata, BasicError> {
pub async fn metadata(&self) -> Result<Metadata, Error> {
let bytes: Bytes = self
.client
.request("state_getMetadata", rpc_params![])
@@ -398,7 +363,7 @@ impl<T: Config> Rpc<T> {
}
/// Fetch system properties
pub async fn system_properties(&self) -> Result<SystemProperties, BasicError> {
pub async fn system_properties(&self) -> Result<SystemProperties, Error> {
Ok(self
.client
.request("system_properties", rpc_params![])
@@ -406,22 +371,22 @@ impl<T: Config> Rpc<T> {
}
/// Fetch system health
pub async fn system_health(&self) -> Result<Health, BasicError> {
pub async fn system_health(&self) -> Result<Health, Error> {
Ok(self.client.request("system_health", rpc_params![]).await?)
}
/// Fetch system chain
pub async fn system_chain(&self) -> Result<String, BasicError> {
pub async fn system_chain(&self) -> Result<String, Error> {
Ok(self.client.request("system_chain", rpc_params![]).await?)
}
/// Fetch system name
pub async fn system_name(&self) -> Result<String, BasicError> {
pub async fn system_name(&self) -> Result<String, Error> {
Ok(self.client.request("system_name", rpc_params![]).await?)
}
/// Fetch system version
pub async fn system_version(&self) -> Result<String, BasicError> {
pub async fn system_version(&self) -> Result<String, Error> {
Ok(self.client.request("system_version", rpc_params![]).await?)
}
@@ -429,7 +394,7 @@ impl<T: Config> Rpc<T> {
pub async fn system_account_next_index(
&self,
account: &T::AccountId,
) -> Result<T::Index, BasicError> {
) -> Result<T::Index, Error> {
Ok(self
.client
.request("system_accountNextIndex", rpc_params![account])
@@ -440,7 +405,7 @@ impl<T: Config> Rpc<T> {
pub async fn header(
&self,
hash: Option<T::Hash>,
) -> Result<Option<T::Header>, BasicError> {
) -> Result<Option<T::Header>, Error> {
let params = rpc_params![hash];
let header = self.client.request("chain_getHeader", params).await?;
Ok(header)
@@ -450,14 +415,14 @@ impl<T: Config> Rpc<T> {
pub async fn block_hash(
&self,
block_number: Option<BlockNumber>,
) -> Result<Option<T::Hash>, BasicError> {
) -> Result<Option<T::Hash>, Error> {
let params = rpc_params![block_number];
let block_hash = self.client.request("chain_getBlockHash", params).await?;
Ok(block_hash)
}
/// Get a block hash of the latest finalized block
pub async fn finalized_head(&self) -> Result<T::Hash, BasicError> {
pub async fn finalized_head(&self) -> Result<T::Hash, Error> {
let hash = self
.client
.request("chain_getFinalizedHead", rpc_params![])
@@ -469,7 +434,7 @@ impl<T: Config> Rpc<T> {
pub async fn block(
&self,
hash: Option<T::Hash>,
) -> Result<Option<ChainBlock<T>>, BasicError> {
) -> Result<Option<ChainBlock<T>>, Error> {
let params = rpc_params![hash];
let block = self.client.request("chain_getBlock", params).await?;
Ok(block)
@@ -483,7 +448,7 @@ impl<T: Config> Rpc<T> {
pub async fn block_stats(
&self,
block_hash: T::Hash,
) -> Result<Option<BlockStats>, BasicError> {
) -> Result<Option<BlockStats>, Error> {
let params = rpc_params![block_hash];
let stats = self.client.request("dev_getBlockStats", params).await?;
Ok(stats)
@@ -492,9 +457,10 @@ impl<T: Config> Rpc<T> {
/// Get proof of storage entries at a specific block's state.
pub async fn read_proof(
&self,
keys: Vec<StorageKey>,
keys: impl IntoIterator<Item = &[u8]>,
hash: Option<T::Hash>,
) -> Result<ReadProof<T::Hash>, BasicError> {
) -> Result<ReadProof<T::Hash>, Error> {
let keys: Vec<String> = keys.into_iter().map(to_hex).collect();
let params = rpc_params![keys, hash];
let proof = self.client.request("state_getReadProof", params).await?;
Ok(proof)
@@ -504,7 +470,7 @@ impl<T: Config> Rpc<T> {
pub async fn runtime_version(
&self,
at: Option<T::Hash>,
) -> Result<RuntimeVersion, BasicError> {
) -> Result<RuntimeVersion, Error> {
let params = rpc_params![at];
let version = self
.client
@@ -514,7 +480,7 @@ impl<T: Config> Rpc<T> {
}
/// Subscribe to blocks.
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, BasicError> {
pub async fn subscribe_blocks(&self) -> Result<Subscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
@@ -530,7 +496,7 @@ impl<T: Config> Rpc<T> {
/// Subscribe to finalized blocks.
pub async fn subscribe_finalized_blocks(
&self,
) -> Result<Subscription<T::Header>, BasicError> {
) -> Result<Subscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
@@ -545,7 +511,7 @@ impl<T: Config> Rpc<T> {
/// Subscribe to runtime version updates that produce changes in the metadata.
pub async fn subscribe_runtime_version(
&self,
) -> Result<Subscription<RuntimeVersion>, BasicError> {
) -> Result<Subscription<RuntimeVersion>, Error> {
let subscription = self
.client
.subscribe(
@@ -561,7 +527,7 @@ impl<T: Config> Rpc<T> {
pub async fn submit_extrinsic<X: Encode>(
&self,
extrinsic: X,
) -> Result<T::Hash, BasicError> {
) -> Result<T::Hash, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = rpc_params![bytes];
let xt_hash = self
@@ -575,8 +541,7 @@ impl<T: Config> Rpc<T> {
pub async fn watch_extrinsic<X: Encode>(
&self,
extrinsic: X,
) -> Result<Subscription<SubstrateTransactionStatus<T::Hash, T::Hash>>, BasicError>
{
) -> Result<Subscription<SubstrateTxStatus<T::Hash, T::Hash>>, Error> {
let bytes: Bytes = extrinsic.encode().into();
let params = rpc_params![bytes];
let subscription = self
@@ -596,14 +561,14 @@ impl<T: Config> Rpc<T> {
key_type: String,
suri: String,
public: Bytes,
) -> Result<(), BasicError> {
) -> Result<(), Error> {
let params = rpc_params![key_type, suri, public];
self.client.request("author_insertKey", params).await?;
Ok(())
}
/// Generate new session keys and returns the corresponding public keys.
pub async fn rotate_keys(&self) -> Result<Bytes, BasicError> {
pub async fn rotate_keys(&self) -> Result<Bytes, Error> {
Ok(self
.client
.request("author_rotateKeys", rpc_params![])
@@ -615,10 +580,7 @@ impl<T: Config> Rpc<T> {
/// `session_keys` is the SCALE encoded session keys object from the runtime.
///
/// Returns `true` iff all private keys could be found.
pub async fn has_session_keys(
&self,
session_keys: Bytes,
) -> Result<bool, BasicError> {
pub async fn has_session_keys(&self, session_keys: Bytes) -> Result<bool, Error> {
let params = rpc_params![session_keys];
Ok(self.client.request("author_hasSessionKeys", params).await?)
}
@@ -630,7 +592,7 @@ impl<T: Config> Rpc<T> {
&self,
public_key: Bytes,
key_type: String,
) -> Result<bool, BasicError> {
) -> Result<bool, Error> {
let params = rpc_params![public_key, key_type];
Ok(self.client.request("author_hasKey", params).await?)
}
@@ -642,8 +604,8 @@ impl<T: Config> Rpc<T> {
&self,
encoded_signed: &[u8],
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, BasicError> {
let params = rpc_params![format!("0x{}", hex::encode(encoded_signed)), at];
) -> Result<ApplyExtrinsicResult, Error> {
let params = rpc_params![to_hex(encoded_signed), at];
let result_bytes: Bytes = self.client.request("system_dryRun", params).await?;
let data: ApplyExtrinsicResult =
codec::Decode::decode(&mut result_bytes.0.as_slice())?;
@@ -669,6 +631,10 @@ async fn ws_transport(url: &str) -> Result<(WsSender, WsReceiver), RpcError> {
.map_err(|e| RpcError::Transport(e.into()))
}
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
#[cfg(test)]
mod test {
use super::*;
-376
View File
@@ -1,376 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Query the runtime storage using [StorageClient].
//!
//! This module is the core of performing runtime storage queries. While you can
//! work with it directly, it's prefer to use the generated `storage()` interface where
//! possible.
//!
//! The exposed API is performing RPC calls to `state_getStorage` and `state_getKeysPaged`.
//!
//! A runtime storage entry can be of type:
//! - [StorageEntryKey::Plain] for keys constructed just from the prefix
//! `twox_128(pallet) ++ twox_128(storage_item)`
//! - [StorageEntryKey::Map] for mapped keys constructed from the prefix,
//! plus other arguments `twox_128(pallet) ++ twox_128(storage_item) ++ hash(arg1) ++ arg1`
//!
//! # Examples
//!
//! ## Fetch Storage Keys
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageClient;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! # // Obtain the storage client wrapper from the API.
//! # let storage: StorageClient<_> = api.client.storage();
//! // Fetch just the keys, returning up to 10 keys.
//! let keys = storage
//! .fetch_keys::<polkadot::xcm_pallet::storage::VersionNotifiers>(10, None, None)
//! .await
//! .unwrap();
//! // Iterate over each key
//! for key in keys.iter() {
//! println!("Key: 0x{}", hex::encode(&key));
//! }
//! # }
//! ```
//!
//! ## Iterate over Storage
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig, PolkadotExtrinsicParams};
//! # use subxt::storage::StorageClient;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
//! pub mod polkadot {}
//!
//! # #[tokio::main]
//! # async fn main() {
//! # let api = ClientBuilder::new()
//! # .build()
//! # .await
//! # .unwrap()
//! # .to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
//! # // Obtain the storage client wrapper from the API.
//! # let storage: StorageClient<_> = api.client.storage();
//! // Iterate over keys and values.
//! let mut iter = storage
//! .iter::<polkadot::xcm_pallet::storage::VersionNotifiers>(None)
//! .await
//! .unwrap();
//! while let Some((key, value)) = iter.next().await.unwrap() {
//! println!("Key: 0x{}", hex::encode(&key));
//! println!("Value: {}", value);
//! }
//! # }
//! ```
use codec::{
Decode,
Encode,
};
use parking_lot::RwLock;
use sp_core::storage::{
StorageChangeSet,
StorageData,
StorageKey,
};
pub use sp_runtime::traits::SignedExtension;
use std::{
marker::PhantomData,
sync::Arc,
};
use crate::{
error::BasicError,
metadata::{
Metadata,
MetadataError,
},
rpc::Rpc,
Config,
StorageHasher,
};
/// Storage entry trait.
pub trait StorageEntry {
/// Pallet name.
const PALLET: &'static str;
/// Storage name.
const STORAGE: &'static str;
/// Type of the storage entry value.
type Value: Decode;
/// Get the key data for the storage.
fn key(&self) -> StorageEntryKey;
}
/// The prefix of the key to a [`StorageEntry`]
pub struct StorageKeyPrefix(Vec<u8>);
impl StorageKeyPrefix {
/// Create the storage key prefix for a [`StorageEntry`]
pub fn new<T: StorageEntry>() -> Self {
let mut bytes = sp_core::twox_128(T::PALLET.as_bytes()).to_vec();
bytes.extend(&sp_core::twox_128(T::STORAGE.as_bytes())[..]);
Self(bytes)
}
/// Convert the prefix into a [`StorageKey`]
pub fn to_storage_key(self) -> StorageKey {
StorageKey(self.0)
}
}
/// Storage key.
pub enum StorageEntryKey {
/// Plain key.
Plain,
/// Map key(s).
Map(Vec<StorageMapKey>),
}
impl StorageEntryKey {
/// Construct the final [`sp_core::storage::StorageKey`] for the storage entry.
pub fn final_key(&self, prefix: StorageKeyPrefix) -> sp_core::storage::StorageKey {
let mut bytes = prefix.0;
if let Self::Map(map_keys) = self {
for map_key in map_keys {
bytes.extend(Self::hash(&map_key.hasher, &map_key.value))
}
}
sp_core::storage::StorageKey(bytes)
}
fn hash(hasher: &StorageHasher, bytes: &[u8]) -> Vec<u8> {
match hasher {
StorageHasher::Identity => bytes.to_vec(),
StorageHasher::Blake2_128 => sp_core::blake2_128(bytes).to_vec(),
StorageHasher::Blake2_128Concat => {
// copied from substrate Blake2_128Concat::hash since StorageHasher is not public
sp_core::blake2_128(bytes)
.iter()
.chain(bytes)
.cloned()
.collect()
}
StorageHasher::Blake2_256 => sp_core::blake2_256(bytes).to_vec(),
StorageHasher::Twox128 => sp_core::twox_128(bytes).to_vec(),
StorageHasher::Twox256 => sp_core::twox_256(bytes).to_vec(),
StorageHasher::Twox64Concat => {
sp_core::twox_64(bytes)
.iter()
.chain(bytes)
.cloned()
.collect()
}
}
}
}
/// Storage key for a Map.
pub struct StorageMapKey {
value: Vec<u8>,
hasher: StorageHasher,
}
impl StorageMapKey {
/// Create a new [`StorageMapKey`] with the encoded data and the hasher.
pub fn new<T: Encode>(value: &T, hasher: StorageHasher) -> Self {
Self {
value: value.encode(),
hasher,
}
}
}
/// Client for querying runtime storage.
pub struct StorageClient<'a, T: Config> {
rpc: &'a Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
iter_page_size: u32,
}
impl<'a, T: Config> Clone for StorageClient<'a, T> {
fn clone(&self) -> Self {
Self {
rpc: self.rpc,
metadata: Arc::clone(&self.metadata),
iter_page_size: self.iter_page_size,
}
}
}
impl<'a, T: Config> StorageClient<'a, T> {
/// Create a new [`StorageClient`]
pub fn new(
rpc: &'a Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
iter_page_size: u32,
) -> Self {
Self {
rpc,
metadata,
iter_page_size,
}
}
/// Fetch the value under an unhashed storage key
pub async fn fetch_unhashed<V: Decode>(
&self,
key: StorageKey,
hash: Option<T::Hash>,
) -> Result<Option<V>, BasicError> {
if let Some(data) = self.rpc.storage(&key, hash).await? {
Ok(Some(Decode::decode(&mut &data.0[..])?))
} else {
Ok(None)
}
}
/// Fetch the raw encoded value under the raw storage key.
pub async fn fetch_raw(
&self,
key: StorageKey,
hash: Option<T::Hash>,
) -> Result<Option<StorageData>, BasicError> {
self.rpc.storage(&key, hash).await
}
/// Fetch a StorageKey with an optional block hash.
pub async fn fetch<F: StorageEntry>(
&self,
store: &F,
hash: Option<T::Hash>,
) -> Result<Option<F::Value>, BasicError> {
let prefix = StorageKeyPrefix::new::<F>();
let key = store.key().final_key(prefix);
self.fetch_unhashed::<F::Value>(key, hash).await
}
/// Fetch a StorageKey that has a default value with an optional block hash.
pub async fn fetch_or_default<F: StorageEntry>(
&self,
store: &F,
hash: Option<T::Hash>,
) -> Result<F::Value, BasicError> {
if let Some(data) = self.fetch(store, hash).await? {
Ok(data)
} else {
let metadata = self.metadata.read();
let pallet_metadata = metadata.pallet(F::PALLET)?;
let storage_metadata = pallet_metadata.storage(F::STORAGE)?;
let default = Decode::decode(&mut &storage_metadata.default[..])
.map_err(MetadataError::DefaultError)?;
Ok(default)
}
}
/// Query historical storage entries
pub async fn query_storage(
&self,
keys: Vec<StorageKey>,
from: T::Hash,
to: Option<T::Hash>,
) -> Result<Vec<StorageChangeSet<T::Hash>>, BasicError> {
self.rpc.query_storage(keys, from, to).await
}
/// Fetch up to `count` keys for a storage map in lexicographic order.
///
/// Supports pagination by passing a value to `start_key`.
pub async fn fetch_keys<F: StorageEntry>(
&self,
count: u32,
start_key: Option<StorageKey>,
hash: Option<T::Hash>,
) -> Result<Vec<StorageKey>, BasicError> {
let key = StorageKeyPrefix::new::<F>().to_storage_key();
let keys = self
.rpc
.storage_keys_paged(Some(key), count, start_key, hash)
.await?;
Ok(keys)
}
/// Returns an iterator of key value pairs.
pub async fn iter<F: StorageEntry>(
&self,
hash: Option<T::Hash>,
) -> Result<KeyIter<'a, T, F>, BasicError> {
let hash = if let Some(hash) = hash {
hash
} else {
self.rpc
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
};
Ok(KeyIter {
client: self.clone(),
hash,
count: self.iter_page_size,
start_key: None,
buffer: Default::default(),
_marker: PhantomData,
})
}
}
/// Iterates over key value pairs in a map.
pub struct KeyIter<'a, T: Config, F: StorageEntry> {
client: StorageClient<'a, T>,
_marker: PhantomData<F>,
count: u32,
hash: T::Hash,
start_key: Option<StorageKey>,
buffer: Vec<(StorageKey, StorageData)>,
}
impl<'a, T: Config, F: StorageEntry> KeyIter<'a, T, F> {
/// Returns the next key value pair from a map.
pub async fn next(&mut self) -> Result<Option<(StorageKey, F::Value)>, BasicError> {
loop {
if let Some((k, v)) = self.buffer.pop() {
return Ok(Some((k, Decode::decode(&mut &v.0[..])?)))
} else {
let keys = self
.client
.fetch_keys::<F>(self.count, self.start_key.take(), Some(self.hash))
.await?;
if keys.is_empty() {
return Ok(None)
}
self.start_key = keys.last().cloned();
let change_sets = self
.client
.rpc
.query_storage_at(&keys, Some(self.hash))
.await?;
for change_set in change_sets {
for (k, v) in change_set.changes {
if let Some(v) = v {
self.buffer.push((k, v));
}
}
}
debug_assert_eq!(self.buffer.len(), keys.len());
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types associated with accessing and working with storage items.
mod storage_address;
mod storage_client;
mod storage_map_key;
pub mod utils;
pub use storage_client::{
KeyIter,
StorageClient,
};
// Re-export as this is used in the public API:
pub use sp_core::storage::StorageKey;
/// Types representing an address which describes where a storage
/// entry lives and how to properly decode it.
pub mod address {
pub use super::{
storage_address::{
dynamic,
dynamic_root,
DynamicStorageAddress,
StaticStorageAddress,
StorageAddress,
Yes,
},
storage_map_key::{
StorageHasher,
StorageMapKey,
},
};
}
// For consistency with other modules, also expose
// the basic address stuff at the root of the module.
pub use storage_address::{
dynamic,
dynamic_root,
DynamicStorageAddress,
StaticStorageAddress,
StorageAddress,
};
+286
View File
@@ -0,0 +1,286 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_map_key::StorageMapKey;
use crate::{
dynamic::{
DecodedValue,
Value,
},
error::{
Error,
StorageAddressError,
},
metadata::{
DecodeWithMetadata,
EncodeWithMetadata,
Metadata,
},
};
use frame_metadata::StorageEntryType;
use scale_info::TypeDef;
use std::borrow::Cow;
// We use this type a bunch, so export it from here.
pub use frame_metadata::StorageHasher;
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
pub trait StorageAddress {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// Can an entry be fetched from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsFetchable;
/// Can a default entry be obtained from this address?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsDefaultable;
/// Can this address be iterated over?
/// Set this type to [`Yes`] to enable the corresponding calls to be made.
type IsIterable;
/// The name of the pallet that the entry lives under.
fn pallet_name(&self) -> &str;
/// The name of the entry in a given pallet that the item is at.
fn entry_name(&self) -> &str;
/// Output the non-prefix bytes; that is, any additional bytes that need
/// to be appended to the key to dig into maps.
fn append_entry_bytes(
&self,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error>;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// Used to signal whether a [`StorageAddress`] can be iterated,
/// fetched and returned with a default value in the type system.
pub struct Yes;
/// This represents a statically generated storage lookup address.
pub struct StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable> {
pallet_name: &'static str,
entry_name: &'static str,
// How to access the specific value at that storage address.
storage_entry_keys: Vec<StorageMapKey>,
// Hash provided from static code for validation.
validation_hash: Option<[u8; 32]>,
_marker: std::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
}
impl<ReturnTy, Fetchable, Defaultable, Iterable>
StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable>
where
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`StaticStorageAddress`] that will be validated
/// against node metadata using the hash given.
pub fn new(
pallet_name: &'static str,
entry_name: &'static str,
storage_entry_keys: Vec<StorageMapKey>,
hash: [u8; 32],
) -> Self {
Self {
pallet_name,
entry_name,
storage_entry_keys,
validation_hash: Some(hash),
_marker: std::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Return bytes representing this storage entry.
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
super::utils::write_storage_address_root_bytes(self, &mut bytes);
for entry in &self.storage_entry_keys {
entry.to_bytes(&mut bytes);
}
bytes
}
/// Return bytes representing the root of this storage entry (ie a hash of
/// the pallet and entry name).
pub fn to_root_bytes(&self) -> Vec<u8> {
super::utils::storage_address_root_bytes(self)
}
}
impl<ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
for StaticStorageAddress<ReturnTy, Fetchable, Defaultable, Iterable>
where
ReturnTy: DecodeWithMetadata,
{
type Target = ReturnTy;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
type IsFetchable = Fetchable;
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn entry_name(&self) -> &str {
self.entry_name
}
fn append_entry_bytes(
&self,
_metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
for entry in &self.storage_entry_keys {
entry.to_bytes(bytes);
}
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// This represents a dynamically generated storage address.
pub struct DynamicStorageAddress<'a, Encodable> {
pallet_name: Cow<'a, str>,
entry_name: Cow<'a, str>,
storage_entry_keys: Vec<Encodable>,
}
/// Construct a new dynamic storage lookup to the root of some entry.
pub fn dynamic_root<'a>(
pallet_name: impl Into<Cow<'a, str>>,
entry_name: impl Into<Cow<'a, str>>,
) -> DynamicStorageAddress<'a, Value> {
DynamicStorageAddress {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
storage_entry_keys: vec![],
}
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<'a, Encodable: EncodeWithMetadata>(
pallet_name: impl Into<Cow<'a, str>>,
entry_name: impl Into<Cow<'a, str>>,
storage_entry_keys: Vec<Encodable>,
) -> DynamicStorageAddress<'a, Encodable> {
DynamicStorageAddress {
pallet_name: pallet_name.into(),
entry_name: entry_name.into(),
storage_entry_keys,
}
}
impl<'a, Encodable> StorageAddress for DynamicStorageAddress<'a, Encodable>
where
Encodable: EncodeWithMetadata,
{
type Target = DecodedValue;
// For dynamic types, we have no static guarantees about any of
// this stuff, so we just allow it and let it fail at runtime:
type IsFetchable = Yes;
type IsDefaultable = Yes;
type IsIterable = Yes;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn append_entry_bytes(
&self,
metadata: &Metadata,
bytes: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let storage = pallet.storage(&self.entry_name)?;
match &storage.ty {
StorageEntryType::Plain(_) => {
if !self.storage_entry_keys.is_empty() {
Err(StorageAddressError::WrongNumberOfKeys {
expected: 0,
actual: self.storage_entry_keys.len(),
}
.into())
} else {
Ok(())
}
}
StorageEntryType::Map { hashers, key, .. } => {
let ty = metadata
.resolve_type(key.id())
.ok_or_else(|| StorageAddressError::TypeNotFound(key.id()))?;
// If the key is a tuple, we encode each value to the corresponding tuple type.
// If the key is not a tuple, encode a single value to the key type.
let type_ids = match ty.type_def() {
TypeDef::Tuple(tuple) => {
tuple.fields().iter().map(|f| f.id()).collect()
}
_other => {
vec![key.id()]
}
};
if type_ids.len() != self.storage_entry_keys.len() {
return Err(StorageAddressError::WrongNumberOfKeys {
expected: type_ids.len(),
actual: self.storage_entry_keys.len(),
}
.into())
}
if hashers.len() == 1 {
// One hasher; hash a tuple of all SCALE encoded bytes with the one hash function.
let mut input = Vec::new();
for (key, type_id) in self.storage_entry_keys.iter().zip(type_ids) {
key.encode_with_metadata(type_id, metadata, &mut input)?;
}
super::storage_map_key::hash_bytes(&input, &hashers[0], bytes);
Ok(())
} else if hashers.len() == type_ids.len() {
// A hasher per field; encode and hash each field independently.
for ((key, type_id), hasher) in
self.storage_entry_keys.iter().zip(type_ids).zip(hashers)
{
let mut input = Vec::new();
key.encode_with_metadata(type_id, metadata, &mut input)?;
super::storage_map_key::hash_bytes(&input, hasher, bytes);
}
Ok(())
} else {
// Mismatch; wrong number of hashers/fields.
Err(StorageAddressError::WrongNumberOfHashers {
hashers: hashers.len(),
fields: type_ids.len(),
}
.into())
}
}
}
}
}
+410
View File
@@ -0,0 +1,410 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::storage_address::{
StorageAddress,
Yes,
};
use crate::{
client::{
OfflineClientT,
OnlineClientT,
},
error::Error,
metadata::{
DecodeWithMetadata,
Metadata,
},
Config,
};
use derivative::Derivative;
use frame_metadata::StorageEntryType;
use scale_info::form::PortableForm;
use sp_core::storage::{
StorageData,
StorageKey,
};
use std::{
future::Future,
marker::PhantomData,
};
/// Query the runtime storage.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct StorageClient<T, Client> {
client: Client,
_marker: PhantomData<T>,
}
impl<T, Client> StorageClient<T, Client> {
/// Create a new [`StorageClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomData,
}
}
}
impl<T, Client> StorageClient<T, Client>
where
T: Config,
Client: OfflineClientT<T>,
{
/// Run the validation logic against some storage address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or storage entry in question do not exist at all).
pub fn validate<Address: StorageAddress>(
&self,
address: &Address,
) -> Result<(), Error> {
if let Some(hash) = address.validation_hash() {
validate_storage(
address.pallet_name(),
address.entry_name(),
hash,
&self.client.metadata(),
)?;
}
Ok(())
}
}
impl<T, Client> StorageClient<T, Client>
where
T: Config,
Client: OnlineClientT<T>,
{
/// Fetch the raw encoded value at the address/key given.
pub fn fetch_raw<'a>(
&self,
key: &'a [u8],
hash: Option<T::Hash>,
) -> impl Future<Output = Result<Option<Vec<u8>>, Error>> + 'a {
let client = self.client.clone();
// Ensure that the returned future doesn't have a lifetime tied to api.storage(),
// which is a temporary thing we'll be throwing away quickly:
async move {
let data = client.rpc().storage(key, hash).await?;
Ok(data.map(|d| d.0))
}
}
/// Fetch a decoded value from storage at a given address and optional block hash.
///
/// # Example
///
/// ```no_run
/// use subxt::{ PolkadotConfig, OnlineClient };
///
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
/// pub mod polkadot {}
///
/// # #[tokio::main]
/// # async fn main() {
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// // Address to a storage entry we'd like to access.
/// let address = polkadot::storage().xcm_pallet().queries(&12345);
///
/// // Fetch just the keys, returning up to 10 keys.
/// let value = api
/// .storage()
/// .fetch(&address, None)
/// .await
/// .unwrap();
///
/// println!("Value: {:?}", value);
/// # }
/// ```
pub fn fetch<'a, Address>(
&self,
address: &'a Address,
hash: Option<T::Hash>,
) -> impl Future<
Output = Result<Option<<Address::Target as DecodeWithMetadata>::Target>, Error>,
> + 'a
where
Address: StorageAddress<IsFetchable = Yes> + 'a,
{
let client = self.clone();
async move {
// Metadata validation checks whether the static address given
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
client.validate(address)?;
// Look up the return type ID to enable DecodeWithMetadata:
let metadata = client.client.metadata();
let lookup_bytes = super::utils::storage_address_bytes(address, &metadata)?;
if let Some(data) = client
.client
.storage()
.fetch_raw(&lookup_bytes, hash)
.await?
{
let val = <Address::Target as DecodeWithMetadata>::decode_storage_with_metadata(
&mut &*data,
address.pallet_name(),
address.entry_name(),
&metadata,
)?;
Ok(Some(val))
} else {
Ok(None)
}
}
}
/// Fetch a StorageKey that has a default value with an optional block hash.
pub fn fetch_or_default<'a, Address>(
&self,
address: &'a Address,
hash: Option<T::Hash>,
) -> impl Future<Output = Result<<Address::Target as DecodeWithMetadata>::Target, Error>>
+ 'a
where
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'a,
{
let client = self.client.clone();
async move {
let pallet_name = address.pallet_name();
let storage_name = address.entry_name();
// Metadata validation happens via .fetch():
if let Some(data) = client.storage().fetch(address, hash).await? {
Ok(data)
} else {
let metadata = client.metadata();
// We have to dig into metadata already, so no point using the optimised `decode_storage_with_metadata` call.
let pallet_metadata = metadata.pallet(pallet_name)?;
let storage_metadata = pallet_metadata.storage(storage_name)?;
let return_ty_id =
return_type_from_storage_entry_type(&storage_metadata.ty);
let bytes = &mut &storage_metadata.default[..];
let val = <Address::Target as DecodeWithMetadata>::decode_with_metadata(
bytes,
return_ty_id,
&metadata,
)?;
Ok(val)
}
}
}
/// Fetch up to `count` keys for a storage map in lexicographic order.
///
/// Supports pagination by passing a value to `start_key`.
pub fn fetch_keys<'a>(
&self,
key: &'a [u8],
count: u32,
start_key: Option<&'a [u8]>,
hash: Option<T::Hash>,
) -> impl Future<Output = Result<Vec<StorageKey>, Error>> + 'a {
let client = self.client.clone();
async move {
let keys = client
.rpc()
.storage_keys_paged(key, count, start_key, hash)
.await?;
Ok(keys)
}
}
/// Returns an iterator of key value pairs.
///
/// ```no_run
/// use subxt::{ PolkadotConfig, OnlineClient };
///
/// #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
/// pub mod polkadot {}
///
/// # #[tokio::main]
/// # async fn main() {
/// let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
///
/// // Address to the root of a storage entry that we'd like to iterate over.
/// let address = polkadot::storage().xcm_pallet().version_notifiers_root();
///
/// // Iterate over keys and values at that address.
/// let mut iter = api
/// .storage()
/// .iter(address, 10, None)
/// .await
/// .unwrap();
///
/// while let Some((key, value)) = iter.next().await.unwrap() {
/// println!("Key: 0x{}", hex::encode(&key));
/// println!("Value: {}", value);
/// }
/// # }
/// ```
pub fn iter<Address>(
&self,
address: Address,
page_size: u32,
hash: Option<T::Hash>,
) -> impl Future<Output = Result<KeyIter<T, Client, Address::Target>, Error>> + 'static
where
Address: StorageAddress<IsIterable = Yes> + 'static,
{
let client = self.clone();
async move {
// Metadata validation checks whether the static address given
// is likely to actually correspond to a real storage entry or not.
// if not, it means static codegen doesn't line up with runtime
// metadata.
client.validate(&address)?;
// Fetch a concrete block hash to iterate over. We do this so that if new blocks
// are produced midway through iteration, we continue to iterate at the block
// we started with and not the new block.
let hash = if let Some(hash) = hash {
hash
} else {
client
.client
.rpc()
.block_hash(None)
.await?
.expect("didn't pass a block number; qed")
};
let metadata = client.client.metadata();
// Look up the return type for flexible decoding. Do this once here to avoid
// potentially doing it every iteration if we used `decode_storage_with_metadata`
// in the iterator.
let return_type_id = lookup_storage_return_type(
&metadata,
address.pallet_name(),
address.entry_name(),
)?;
// The root pallet/entry bytes for this storage entry:
let address_root_bytes = super::utils::storage_address_root_bytes(&address);
Ok(KeyIter {
client,
address_root_bytes,
metadata,
return_type_id,
block_hash: hash,
count: page_size,
start_key: None,
buffer: Default::default(),
_marker: std::marker::PhantomData,
})
}
}
}
/// Iterates over key value pairs in a map.
pub struct KeyIter<T: Config, Client, ReturnTy> {
client: StorageClient<T, Client>,
address_root_bytes: Vec<u8>,
return_type_id: u32,
metadata: Metadata,
count: u32,
block_hash: T::Hash,
start_key: Option<StorageKey>,
buffer: Vec<(StorageKey, StorageData)>,
_marker: std::marker::PhantomData<ReturnTy>,
}
impl<'a, T: Config, Client: OnlineClientT<T>, ReturnTy> KeyIter<T, Client, ReturnTy>
where
T: Config,
Client: OnlineClientT<T>,
ReturnTy: DecodeWithMetadata,
{
/// Returns the next key value pair from a map.
pub async fn next(
&mut self,
) -> Result<Option<(StorageKey, ReturnTy::Target)>, Error> {
loop {
if let Some((k, v)) = self.buffer.pop() {
let val = ReturnTy::decode_with_metadata(
&mut &v.0[..],
self.return_type_id,
&self.metadata,
)?;
return Ok(Some((k, val)))
} else {
let start_key = self.start_key.take();
let keys = self
.client
.fetch_keys(
&self.address_root_bytes,
self.count,
start_key.as_ref().map(|k| &*k.0),
Some(self.block_hash),
)
.await?;
if keys.is_empty() {
return Ok(None)
}
self.start_key = keys.last().cloned();
let change_sets = self
.client
.client
.rpc()
.query_storage_at(keys.iter().map(|k| &*k.0), Some(self.block_hash))
.await?;
for change_set in change_sets {
for (k, v) in change_set.changes {
if let Some(v) = v {
self.buffer.push((k, v));
}
}
}
debug_assert_eq!(self.buffer.len(), keys.len());
}
}
}
}
/// Validate a storage entry against the metadata.
fn validate_storage(
pallet_name: &str,
storage_name: &str,
hash: [u8; 32],
metadata: &Metadata,
) -> Result<(), Error> {
let expected_hash = match metadata.storage_hash(pallet_name, storage_name) {
Ok(hash) => hash,
Err(e) => return Err(e.into()),
};
match expected_hash == hash {
true => Ok(()),
false => Err(crate::error::MetadataError::IncompatibleMetadata.into()),
}
}
/// look up a return type ID for some storage entry.
fn lookup_storage_return_type(
metadata: &Metadata,
pallet: &str,
entry: &str,
) -> Result<u32, Error> {
let storage_entry_type = &metadata.pallet(pallet)?.storage(entry)?.ty;
Ok(return_type_from_storage_entry_type(storage_entry_type))
}
/// Fetch the return type out of a [`StorageEntryType`].
fn return_type_from_storage_entry_type(entry: &StorageEntryType<PortableForm>) -> u32 {
match entry {
StorageEntryType::Plain(ty) => ty.id(),
StorageEntryType::Map { value, .. } => value.id(),
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use codec::Encode;
pub use sp_runtime::traits::SignedExtension;
// We use this type a bunch, so export it from here.
pub use frame_metadata::StorageHasher;
/// Storage key for a Map.
#[derive(Clone)]
pub struct StorageMapKey {
value: Vec<u8>,
hasher: StorageHasher,
}
impl StorageMapKey {
/// Create a new [`StorageMapKey`] by pre-encoding static data and pairing it with a hasher.
pub fn new<Encodable: Encode>(
value: Encodable,
hasher: StorageHasher,
) -> StorageMapKey {
Self {
value: value.encode(),
hasher,
}
}
/// Convert this [`StorageMapKey`] into bytes and append them to some existing bytes.
pub fn to_bytes(&self, bytes: &mut Vec<u8>) {
hash_bytes(&self.value, &self.hasher, bytes)
}
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub(super) fn hash_bytes(input: &[u8], hasher: &StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_core::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_core::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_core::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_core::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_core::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_core::twox_64(input));
bytes.extend(input);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! these utility methods complement the [`StorageAddress`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use super::StorageAddress;
use crate::{
error::Error,
metadata::Metadata,
};
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
addr: &Address,
out: &mut Vec<u8>,
) {
out.extend(&sp_core::twox_128(addr.pallet_name().as_bytes()));
out.extend(&sp_core::twox_128(addr.entry_name().as_bytes()));
}
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
/// a lookup in a storage map at that location.
pub fn storage_address_bytes<Address: StorageAddress>(
addr: &Address,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
addr.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
let mut bytes = Vec::new();
write_storage_address_root_bytes(addr, &mut bytes);
bytes
}
@@ -20,6 +20,9 @@
mod params;
mod signer;
mod tx_client;
mod tx_payload;
mod tx_progress;
pub use self::{
params::{
@@ -38,4 +41,20 @@ pub use self::{
PairSigner,
Signer,
},
tx_client::{
SignedSubmittableExtrinsic,
TxClient,
},
tx_payload::{
dynamic,
DynamicTxPayload,
StaticTxPayload,
TxPayload,
},
tx_progress::{
TxEvents,
TxInBlock,
TxProgress,
TxStatus,
},
};
@@ -2,16 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
utils::Encoded,
Config,
};
use codec::{
Compact,
Encode,
};
use core::fmt::Debug;
use crate::{
Config,
Encoded,
};
use derivative::Derivative;
// We require Era as a param below, so make it available from here.
pub use sp_runtime::generic::Era;
@@ -20,7 +20,7 @@ pub use sp_runtime::generic::Era;
/// "additional" parameters that are signed and used in transactions.
/// see [`BaseExtrinsicParams`] for an implementation that is compatible with
/// a Polkadot node.
pub trait ExtrinsicParams<T: Config>: Debug {
pub trait ExtrinsicParams<Index, Hash>: Debug + 'static {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
@@ -30,8 +30,8 @@ pub trait ExtrinsicParams<T: Config>: Debug {
fn new(
spec_version: u32,
tx_version: u32,
nonce: T::Index,
genesis_hash: T::Hash,
nonce: Index,
genesis_hash: Hash,
other_params: Self::OtherParams,
) -> Self;
@@ -73,7 +73,8 @@ pub type PolkadotExtrinsicParamsBuilder<T> = BaseExtrinsicParamsBuilder<T, Plain
/// If your node differs in the "signed extra" and "additional" parameters expected
/// to be sent/signed with a transaction, then you can define your own type which
/// implements the [`ExtrinsicParams`] trait.
#[derive(Debug)]
#[derive(Derivative)]
#[derivative(Debug(bound = "Tip: Debug"))]
pub struct BaseExtrinsicParams<T: Config, Tip: Debug> {
era: Era,
nonce: T::Index,
@@ -91,6 +92,13 @@ pub struct BaseExtrinsicParams<T: Config, Tip: Debug> {
///
/// Prefer to use [`SubstrateExtrinsicParamsBuilder`] for a version of this tailored towards
/// Substrate, or [`PolkadotExtrinsicParamsBuilder`] for a version tailored to Polkadot.
#[derive(Derivative)]
#[derivative(
Debug(bound = "Tip: Debug"),
Clone(bound = "Tip: Clone"),
Copy(bound = "Tip: Copy"),
PartialEq(bound = "Tip: PartialEq")
)]
pub struct BaseExtrinsicParamsBuilder<T: Config, Tip> {
era: Era,
mortality_checkpoint: Option<T::Hash>,
@@ -132,7 +140,9 @@ impl<T: Config, Tip: Default> Default for BaseExtrinsicParamsBuilder<T, Tip> {
}
}
impl<T: Config, Tip: Debug + Encode> ExtrinsicParams<T> for BaseExtrinsicParams<T, Tip> {
impl<T: Config, Tip: Debug + Encode + 'static> ExtrinsicParams<T::Index, T::Hash>
for BaseExtrinsicParams<T, Tip>
{
type OtherParams = BaseExtrinsicParamsBuilder<T, Tip>;
fn new(
+331
View File
@@ -0,0 +1,331 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::TxPayload;
use crate::{
client::{
OfflineClientT,
OnlineClientT,
},
error::Error,
tx::{
ExtrinsicParams,
Signer,
TxProgress,
},
utils::{
Encoded,
PhantomDataSendSync,
},
Config,
};
use codec::{
Compact,
Encode,
};
use derivative::Derivative;
use sp_runtime::{
traits::Hash,
ApplyExtrinsicResult,
};
/// A client for working with transactions.
#[derive(Derivative)]
#[derivative(Clone(bound = "Client: Clone"))]
pub struct TxClient<T: Config, Client> {
client: Client,
_marker: PhantomDataSendSync<T>,
}
impl<T: Config, Client> TxClient<T, Client> {
/// Create a new [`TxClient`]
pub fn new(client: Client) -> Self {
Self {
client,
_marker: PhantomDataSendSync::new(),
}
}
}
impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
/// the pallet or call in question do not exist at all).
pub fn validate<Call>(&self, call: &Call) -> Result<(), Error>
where
Call: TxPayload,
{
if let Some(actual_hash) = call.validation_hash() {
let metadata = self.client.metadata();
let expected_hash =
metadata.call_hash(call.pallet_name(), call.call_name())?;
if actual_hash != expected_hash {
return Err(crate::metadata::MetadataError::IncompatibleMetadata.into())
}
}
Ok(())
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
where
Call: TxPayload,
{
let metadata = self.client.metadata();
let mut bytes = Vec::new();
call.encode_call_data(&metadata, &mut bytes)?;
Ok(bytes)
}
/// Creates a raw signed extrinsic, without submitting it.
pub async fn create_signed_with_nonce<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
account_nonce: T::Index,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<SignedSubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
self.validate(call)?;
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = Encoded(self.call_data(call)?);
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = {
// Obtain spec version and transaction version from the runtime version of the client.
let runtime = self.client.runtime_version();
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::new(
runtime.spec_version,
runtime.transaction_version,
account_nonce,
self.client.genesis_hash(),
other_params,
)
};
tracing::debug!(
"tx additional_and_extra_params: {:?}",
additional_and_extra_params
);
// 4. Construct signature. This is compatible with the Encode impl
// for SignedPayload (which is this payload of bytes that we'd like)
// to sign. See:
// https://github.com/paritytech/substrate/blob/9a6d706d8db00abb6ba183839ec98ecd9924b1f8/primitives/runtime/src/generic/unchecked_extrinsic.rs#L215)
let signature = {
let mut bytes = Vec::new();
call_data.encode_to(&mut bytes);
additional_and_extra_params.encode_extra_to(&mut bytes);
additional_and_extra_params.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
signer.sign(&sp_core::blake2_256(&bytes))
} else {
signer.sign(&bytes)
}
};
tracing::debug!("tx signature: {}", hex::encode(signature.encode()));
// 5. Encode extrinsic, now that we have the parts we need. This is compatible
// with the Encode impl for UncheckedExtrinsic (protocol version 4).
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
signer.address().encode_to(&mut encoded_inner);
// the signature bytes
signature.encode_to(&mut encoded_inner);
// attach custom extra params
additional_and_extra_params.encode_extra_to(&mut encoded_inner);
// and now, call data
call_data.encode_to(&mut encoded_inner);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
.expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
// maybe we can just return the raw bytes..
Ok(SignedSubmittableExtrinsic {
client: self.client.clone(),
encoded: Encoded(extrinsic),
marker: std::marker::PhantomData,
})
}
}
impl<T: Config, C: OnlineClientT<T>> TxClient<T, C> {
/// Creates a raw signed extrinsic, without submitting it.
pub async fn create_signed<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<SignedSubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
{
// Get nonce from the node.
let account_nonce = if let Some(nonce) = signer.nonce() {
nonce
} else {
self.client
.rpc()
.system_account_next_index(signer.account_id())
.await?
};
self.create_signed_with_nonce(call, signer, account_nonce, other_params)
.await
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch_default<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
{
self.sign_and_submit_then_watch(call, signer, Default::default())
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
{
self.create_signed(call, signer, other_params)
.await?
.submit_and_watch()
.await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<T::Hash, Error>
where
Call: TxPayload,
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
{
self.sign_and_submit(call, signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit<Call>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
{
self.create_signed(call, signer, other_params)
.await?
.submit()
.await
}
}
/// This represents an extrinsic that has been signed and is ready to submit.
pub struct SignedSubmittableExtrinsic<T, C> {
client: C,
encoded: Encoded,
marker: std::marker::PhantomData<T>,
}
impl<T, C> SignedSubmittableExtrinsic<T, C>
where
T: Config,
C: OnlineClientT<T>,
{
/// Submits the extrinsic to the chain.
///
/// Returns a [`TxProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, Error> {
// Get a hash of the extrinsic (we'll need this later).
let ext_hash = T::Hashing::hash_of(&self.encoded);
// Submit and watch for transaction progress.
let sub = self.client.rpc().watch_extrinsic(&self.encoded).await?;
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
}
/// Submits the extrinsic to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn submit(&self) -> Result<T::Hash, Error> {
self.client.rpc().submit_extrinsic(&self.encoded).await
}
/// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
///
/// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
pub async fn dry_run(
&self,
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, Error> {
self.client.rpc().dry_run(self.encoded(), at).await
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
}
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::{
dynamic::Value,
error::{
Error,
MetadataError,
},
metadata::Metadata,
};
use codec::Encode;
use std::borrow::Cow;
/// This represents a transaction payload that can be submitted
/// to a node.
pub trait TxPayload {
/// The name of the pallet that the call lives under.
fn pallet_name(&self) -> &str;
/// The name of the call.
fn call_name(&self) -> &str;
/// Encode call data to the provided output.
fn encode_call_data(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error>;
/// An optional validation hash that can be provided
/// to verify that the shape of the call on the node
/// aligns with our expectations.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents a statically generated transaction payload.
pub struct StaticTxPayload<CallData> {
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
}
impl<CallData> StaticTxPayload<CallData> {
/// Create a new [`StaticTxPayload`] from static data.
pub fn new(
pallet_name: &'static str,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
StaticTxPayload {
pallet_name,
call_name,
call_data,
validation_hash: Some(validation_hash),
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
}
impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
fn pallet_name(&self) -> &str {
self.pallet_name
}
fn call_name(&self) -> &str {
self.call_name
}
fn encode_call_data(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(self.pallet_name)?;
let pallet_index = pallet.index();
let call_index = pallet.call_index(self.call_name)?;
pallet_index.encode_to(out);
call_index.encode_to(out);
self.call_data.encode_to(out);
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// This represents a dynamically generated transaction payload.
pub struct DynamicTxPayload<'a> {
pallet_name: Cow<'a, str>,
call_name: Cow<'a, str>,
fields: Vec<Value<()>>,
}
/// Construct a new dynamic transaction payload to submit to a node.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
call_name: impl Into<Cow<'a, str>>,
fields: Vec<Value<()>>,
) -> DynamicTxPayload<'a> {
DynamicTxPayload {
pallet_name: pallet_name.into(),
call_name: call_name.into(),
fields,
}
}
impl<'a> TxPayload for DynamicTxPayload<'a> {
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn call_name(&self) -> &str {
&self.call_name
}
fn encode_call_data(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let call_id = pallet.call_ty_id().ok_or(MetadataError::CallNotFound)?;
let call_value =
Value::unnamed_variant(self.call_name.to_owned(), self.fields.clone());
pallet.index().encode_to(out);
scale_value::scale::encode_as_type(&call_value, call_id, metadata.types(), out)?;
Ok(())
}
}
@@ -2,32 +2,27 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Types representing extrinsics/transactions that have been submitted to a node.
use std::task::Poll;
use crate::PhantomDataSendSync;
use codec::Decode;
use sp_runtime::traits::Hash;
pub use sp_runtime::traits::SignedExtension;
use crate::{
client::Client,
client::OnlineClientT,
error::{
BasicError,
DispatchError,
Error,
HasModuleError,
ModuleError,
RuntimeError,
TransactionError,
},
events::{
self,
EventDetails,
Events,
RawEventDetails,
EventsClient,
Phase,
StaticEvent,
},
rpc::SubstrateTransactionStatus,
rpc::SubstrateTxStatus,
Config,
Phase,
};
use derivative::Derivative;
use futures::{
@@ -38,71 +33,65 @@ use jsonrpsee::core::{
client::Subscription as RpcSubscription,
Error as RpcError,
};
use sp_runtime::traits::Hash;
/// This struct represents a subscription to the progress of some transaction, and is
/// returned from [`crate::SubmittableExtrinsic::sign_and_submit_then_watch()`].
pub use sp_runtime::traits::SignedExtension;
/// This struct represents a subscription to the progress of some transaction.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionProgress<'client, T: Config, E, Evs> {
sub: Option<RpcSubscription<SubstrateTransactionStatus<T::Hash, T::Hash>>>,
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
pub struct TxProgress<T: Config, C> {
sub: Option<RpcSubscription<SubstrateTxStatus<T::Hash, T::Hash>>>,
ext_hash: T::Hash,
client: &'client Client<T>,
_error: PhantomDataSendSync<(E, Evs)>,
client: C,
}
// The above type is not `Unpin` by default unless the generic param `T` is,
// so we manually make it clear that Unpin is actually fine regardless of `T`
// (we don't care if this moves around in memory while it's "pinned").
impl<'client, T: Config, E, Evs> Unpin for TransactionProgress<'client, T, E, Evs> {}
impl<T: Config, C> Unpin for TxProgress<T, C> {}
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
TransactionProgress<'client, T, E, Evs>
{
/// Instantiate a new [`TransactionProgress`] from a custom subscription.
impl<T: Config, C> TxProgress<T, C> {
/// Instantiate a new [`TxProgress`] from a custom subscription.
pub fn new(
sub: RpcSubscription<SubstrateTransactionStatus<T::Hash, T::Hash>>,
client: &'client Client<T>,
sub: RpcSubscription<SubstrateTxStatus<T::Hash, T::Hash>>,
client: C,
ext_hash: T::Hash,
) -> Self {
Self {
sub: Some(sub),
client,
ext_hash,
_error: PhantomDataSendSync::new(),
}
}
}
impl<T: Config, C: OnlineClientT<T>> TxProgress<T, C> {
/// Return the next transaction status when it's emitted. This just delegates to the
/// [`futures::Stream`] implementation for [`TransactionProgress`], but allows you to
/// [`futures::Stream`] implementation for [`TxProgress`], but allows you to
/// avoid importing that trait if you don't otherwise need it.
pub async fn next_item(
&mut self,
) -> Option<Result<TransactionStatus<'client, T, E, Evs>, BasicError>> {
pub async fn next_item(&mut self) -> Option<Result<TxStatus<T, C>, Error>> {
self.next().await
}
/// Wait for the transaction to be in a block (but not necessarily finalized), and return
/// an [`TransactionInBlock`] instance when this happens, or an error if there was a problem
/// an [`TxInBlock`] instance when this happens, or an error if there was a problem
/// waiting for this to happen.
///
/// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the
/// transaction progresses, use [`TransactionProgress::next_item()`] instead.
/// transaction progresses, use [`TxProgress::next_item()`] instead.
///
/// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they
/// may well indicate with some probability that the transaction will not make it into a block,
/// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower
/// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_in_block(
mut self,
) -> Result<TransactionInBlock<'client, T, E, Evs>, BasicError> {
/// level [`TxProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_in_block(mut self) -> Result<TxInBlock<T, C>, Error> {
while let Some(status) = self.next_item().await {
match status? {
// Finalized or otherwise in a block! Return.
TransactionStatus::InBlock(s) | TransactionStatus::Finalized(s) => {
return Ok(s)
}
TxStatus::InBlock(s) | TxStatus::Finalized(s) => return Ok(s),
// Error scenarios; return the error.
TransactionStatus::FinalityTimeout(_) => {
TxStatus::FinalityTimeout(_) => {
return Err(TransactionError::FinalitySubscriptionTimeout.into())
}
// Ignore anything else and wait for next status event:
@@ -112,25 +101,23 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
Err(RpcError::Custom("RPC subscription dropped".into()).into())
}
/// Wait for the transaction to be finalized, and return a [`TransactionInBlock`]
/// Wait for the transaction to be finalized, and return a [`TxInBlock`]
/// instance when it is, or an error if there was a problem waiting for finalization.
///
/// **Note:** consumes `self`. If you'd like to perform multiple actions as the state of the
/// transaction progresses, use [`TransactionProgress::next_item()`] instead.
/// transaction progresses, use [`TxProgress::next_item()`] instead.
///
/// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they
/// may well indicate with some probability that the transaction will not make it into a block,
/// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower
/// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized(
mut self,
) -> Result<TransactionInBlock<'client, T, E, Evs>, BasicError> {
/// level [`TxProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized(mut self) -> Result<TxInBlock<T, C>, Error> {
while let Some(status) = self.next_item().await {
match status? {
// Finalized! Return.
TransactionStatus::Finalized(s) => return Ok(s),
TxStatus::Finalized(s) => return Ok(s),
// Error scenarios; return the error.
TransactionStatus::FinalityTimeout(_) => {
TxStatus::FinalityTimeout(_) => {
return Err(TransactionError::FinalitySubscriptionTimeout.into())
}
// Ignore and wait for next status event:
@@ -145,24 +132,20 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
/// as well as a couple of other details (block hash and extrinsic hash).
///
/// **Note:** consumes self. If you'd like to perform multiple actions as progress is made,
/// use [`TransactionProgress::next_item()`] instead.
/// use [`TxProgress::next_item()`] instead.
///
/// **Note:** transaction statuses like `Invalid` and `Usurped` are ignored, because while they
/// may well indicate with some probability that the transaction will not make it into a block,
/// there is no guarantee that this is true. Thus, we prefer to "play it safe" here. Use the lower
/// level [`TransactionProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized_success(
self,
) -> Result<TransactionEvents<T, Evs>, Error<E>> {
/// level [`TxProgress::next_item()`] API if you'd like to handle these statuses yourself.
pub async fn wait_for_finalized_success(self) -> Result<TxEvents<T>, Error> {
let evs = self.wait_for_finalized().await?.wait_for_success().await?;
Ok(evs)
}
}
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
for TransactionProgress<'client, T, E, Evs>
{
type Item = Result<TransactionStatus<'client, T, E, Evs>, BasicError>;
impl<T: Config, C: OnlineClientT<T>> Stream for TxProgress<T, C> {
type Item = Result<TxStatus<T, C>, Error>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
@@ -177,28 +160,22 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
.map_err(|e| e.into())
.map_ok(|status| {
match status {
SubstrateTransactionStatus::Future => TransactionStatus::Future,
SubstrateTransactionStatus::Ready => TransactionStatus::Ready,
SubstrateTransactionStatus::Broadcast(peers) => {
TransactionStatus::Broadcast(peers)
}
SubstrateTransactionStatus::InBlock(hash) => {
TransactionStatus::InBlock(TransactionInBlock::new(
SubstrateTxStatus::Future => TxStatus::Future,
SubstrateTxStatus::Ready => TxStatus::Ready,
SubstrateTxStatus::Broadcast(peers) => TxStatus::Broadcast(peers),
SubstrateTxStatus::InBlock(hash) => {
TxStatus::InBlock(TxInBlock::new(
hash,
self.ext_hash,
self.client,
self.client.clone(),
))
}
SubstrateTransactionStatus::Retracted(hash) => {
TransactionStatus::Retracted(hash)
}
SubstrateTransactionStatus::Usurped(hash) => {
TransactionStatus::Usurped(hash)
}
SubstrateTransactionStatus::Dropped => TransactionStatus::Dropped,
SubstrateTransactionStatus::Invalid => TransactionStatus::Invalid,
SubstrateTxStatus::Retracted(hash) => TxStatus::Retracted(hash),
SubstrateTxStatus::Usurped(hash) => TxStatus::Usurped(hash),
SubstrateTxStatus::Dropped => TxStatus::Dropped,
SubstrateTxStatus::Invalid => TxStatus::Invalid,
// Only the following statuses are actually considered "final" (see the substrate
// docs on `TransactionStatus`). Basically, either the transaction makes it into a
// docs on `TxStatus`). Basically, either the transaction makes it into a
// block, or we eventually give up on waiting for it to make it into a block.
// Even `Dropped`/`Invalid`/`Usurped` transactions might make it into a block eventually.
//
@@ -206,16 +183,16 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
// nonce might still be valid on some fork on another node which ends up being finalized.
// Equally, a transaction `Dropped` from one node may still be in the transaction pool,
// and make it into a block, on another node. Likewise with `Usurped`.
SubstrateTransactionStatus::FinalityTimeout(hash) => {
SubstrateTxStatus::FinalityTimeout(hash) => {
self.sub = None;
TransactionStatus::FinalityTimeout(hash)
TxStatus::FinalityTimeout(hash)
}
SubstrateTransactionStatus::Finalized(hash) => {
SubstrateTxStatus::Finalized(hash) => {
self.sub = None;
TransactionStatus::Finalized(TransactionInBlock::new(
TxStatus::Finalized(TxInBlock::new(
hash,
self.ext_hash,
self.client,
self.client.clone(),
))
}
}
@@ -223,12 +200,12 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
}
}
//* Dev note: The below is adapted from the substrate docs on `TransactionStatus`, which this
//* enum was adapted from (and which is an exact copy of `SubstrateTransactionStatus` in this crate).
//* Dev note: The below is adapted from the substrate docs on `TxStatus`, which this
//* enum was adapted from (and which is an exact copy of `SubstrateTxStatus` in this crate).
//* Note that the number of finality watchers is, at the time of writing, found in the constant
//* `MAX_FINALITY_WATCHERS` in the `sc_transaction_pool` crate.
//*
/// Possible transaction statuses returned from our [`TransactionProgress::next_item()`] call.
/// Possible transaction statuses returned from our [`TxProgress::next_item()`] call.
///
/// These status events can be grouped based on their kinds as:
///
@@ -270,8 +247,8 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode> Stream
/// within 512 blocks. This either indicates that finality is not available for your chain,
/// or that finality gadget is lagging behind.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
pub enum TxStatus<T: Config, C> {
/// The transaction is part of the "future" queue.
Future,
/// The transaction is part of the "ready" queue.
@@ -279,7 +256,7 @@ pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
/// The transaction has been broadcast to the given peers.
Broadcast(Vec<String>),
/// The transaction has been included in a block with given hash.
InBlock(TransactionInBlock<'client, T, E, Evs>),
InBlock(TxInBlock<T, C>),
/// The block this transaction was included in has been retracted,
/// probably because it did not make it onto the blocks which were
/// finalized.
@@ -288,7 +265,7 @@ pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
/// blocks, and so the subscription has ended.
FinalityTimeout(T::Hash),
/// The transaction has been finalized by a finality-gadget, e.g GRANDPA.
Finalized(TransactionInBlock<'client, T, E, Evs>),
Finalized(TxInBlock<T, C>),
/// The transaction has been replaced in the pool by another transaction
/// that provides the same tags. (e.g. same (sender, nonce)).
Usurped(T::Hash),
@@ -298,10 +275,10 @@ pub enum TransactionStatus<'client, T: Config, E: Decode, Evs: Decode> {
Invalid,
}
impl<'client, T: Config, E: Decode, Evs: Decode> TransactionStatus<'client, T, E, Evs> {
impl<T: Config, C> TxStatus<T, C> {
/// A convenience method to return the `Finalized` details. Returns
/// [`None`] if the enum variant is not [`TransactionStatus::Finalized`].
pub fn as_finalized(&self) -> Option<&TransactionInBlock<'client, T, E, Evs>> {
/// [`None`] if the enum variant is not [`TxStatus::Finalized`].
pub fn as_finalized(&self) -> Option<&TxInBlock<T, C>> {
match self {
Self::Finalized(val) => Some(val),
_ => None,
@@ -309,8 +286,8 @@ impl<'client, T: Config, E: Decode, Evs: Decode> TransactionStatus<'client, T, E
}
/// A convenience method to return the `InBlock` details. Returns
/// [`None`] if the enum variant is not [`TransactionStatus::InBlock`].
pub fn as_in_block(&self) -> Option<&TransactionInBlock<'client, T, E, Evs>> {
/// [`None`] if the enum variant is not [`TxStatus::InBlock`].
pub fn as_in_block(&self) -> Option<&TxInBlock<T, C>> {
match self {
Self::InBlock(val) => Some(val),
_ => None,
@@ -320,27 +297,19 @@ impl<'client, T: Config, E: Decode, Evs: Decode> TransactionStatus<'client, T, E
/// This struct represents a transaction that has made it into a block.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionInBlock<'client, T: Config, E: Decode, Evs: Decode> {
#[derivative(Debug(bound = "C: std::fmt::Debug"))]
pub struct TxInBlock<T: Config, C> {
block_hash: T::Hash,
ext_hash: T::Hash,
client: &'client Client<T>,
_error: PhantomDataSendSync<(E, Evs)>,
client: C,
}
impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
TransactionInBlock<'client, T, E, Evs>
{
pub(crate) fn new(
block_hash: T::Hash,
ext_hash: T::Hash,
client: &'client Client<T>,
) -> Self {
impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
pub(crate) fn new(block_hash: T::Hash, ext_hash: T::Hash, client: C) -> Self {
Self {
block_hash,
ext_hash,
client,
_error: PhantomDataSendSync::new(),
}
}
@@ -362,34 +331,21 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
/// **Note:** If multiple `ExtrinsicFailed` errors are returned (for instance
/// because a pallet chooses to emit one as an event, which is considered
/// abnormal behaviour), it is not specified which of the errors is returned here.
/// You can use [`TransactionInBlock::fetch_events`] instead if you'd like to
/// You can use [`TxInBlock::fetch_events`] instead if you'd like to
/// work with multiple "error" events.
///
/// **Note:** This has to download block details from the node and decode events
/// from them.
pub async fn wait_for_success(&self) -> Result<TransactionEvents<T, Evs>, Error<E>> {
pub async fn wait_for_success(&self) -> Result<TxEvents<T>, Error> {
let events = self.fetch_events().await?;
// Try to find any errors; return the first one we encounter.
for ev in events.iter_raw() {
for ev in events.iter() {
let ev = ev?;
if &ev.pallet == "System" && &ev.variant == "ExtrinsicFailed" {
let dispatch_error = E::decode(&mut &*ev.bytes)?;
if let Some(error_data) = dispatch_error.module_error_data() {
// Error index is utilized as the first byte from the error array.
let locked_metadata = self.client.metadata();
let metadata = locked_metadata.read();
let details = metadata
.error(error_data.pallet_index, error_data.error_index())?;
return Err(Error::Module(ModuleError {
pallet: details.pallet().to_string(),
error: details.error().to_string(),
description: details.description().to_vec(),
error_data,
}))
} else {
return Err(Error::Runtime(RuntimeError(dispatch_error)))
}
if ev.pallet_name() == "System" && ev.variant_name() == "ExtrinsicFailed" {
let dispatch_error =
DispatchError::decode_from(ev.field_bytes(), &self.client.metadata());
return Err(dispatch_error.into())
}
}
@@ -402,13 +358,13 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
///
/// **Note:** This has to download block details from the node and decode events
/// from them.
pub async fn fetch_events(&self) -> Result<TransactionEvents<T, Evs>, BasicError> {
pub async fn fetch_events(&self) -> Result<TxEvents<T>, Error> {
let block = self
.client
.rpc()
.block(Some(self.block_hash))
.await?
.ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?;
.ok_or(Error::Transaction(TransactionError::BlockHashNotFound))?;
let extrinsic_idx = block.block.extrinsics
.iter()
@@ -418,11 +374,13 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
})
// If we successfully obtain the block hash we think contains our
// extrinsic, the extrinsic should be in there somewhere..
.ok_or(BasicError::Transaction(TransactionError::BlockHashNotFound))?;
.ok_or(Error::Transaction(TransactionError::BlockHashNotFound))?;
let events = events::at::<T, Evs>(self.client, self.block_hash).await?;
let events = EventsClient::new(self.client.clone())
.at(Some(self.block_hash))
.await?;
Ok(TransactionEvents {
Ok(TxEvents {
ext_hash: self.ext_hash,
ext_idx: extrinsic_idx as u32,
events,
@@ -434,13 +392,13 @@ impl<'client, T: Config, E: Decode + HasModuleError, Evs: Decode>
/// We can iterate over the events, or look for a specific one.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct TransactionEvents<T: Config, Evs: Decode> {
pub struct TxEvents<T: Config> {
ext_hash: T::Hash,
ext_idx: u32,
events: Events<T, Evs>,
events: Events<T>,
}
impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
impl<T: Config> TxEvents<T> {
/// Return the hash of the block that the transaction has made it into.
pub fn block_hash(&self) -> T::Hash {
self.events.block_hash()
@@ -452,34 +410,18 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
}
/// Return all of the events in the block that the transaction made it into.
pub fn all_events_in_block(&self) -> &events::Events<T, Evs> {
pub fn all_events_in_block(&self) -> &events::Events<T> {
&self.events
}
/// Iterate over the statically decoded events associated with this transaction.
///
/// This works in the same way that [`events::Events::iter()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<Evs>, BasicError>> + '_ {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase == Phase::ApplyExtrinsic(self.ext_idx))
.unwrap_or(true) // Keep any errors
})
}
/// Iterate over all of the raw events associated with this transaction.
///
/// This works in the same way that [`events::Events::iter_raw()`] does, with the
/// This works in the same way that [`events::Events::iter()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn iter_raw(
&self,
) -> impl Iterator<Item = Result<RawEventDetails, BasicError>> + '_ {
self.events.iter_raw().filter(|ev| {
pub fn iter(&self) -> impl Iterator<Item = Result<EventDetails, Error>> + '_ {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase == Phase::ApplyExtrinsic(self.ext_idx))
.map(|ev| ev.phase() == Phase::ApplyExtrinsic(self.ext_idx))
.unwrap_or(true) // Keep any errors.
})
}
@@ -488,10 +430,8 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
///
/// This works in the same way that [`events::Events::find()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn find<Ev: crate::Event>(
&self,
) -> impl Iterator<Item = Result<Ev, BasicError>> + '_ {
self.iter_raw().filter_map(|ev| {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> + '_ {
self.iter().filter_map(|ev| {
ev.and_then(|ev| ev.as_event::<Ev>().map_err(Into::into))
.transpose()
})
@@ -502,7 +442,7 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
///
/// This works in the same way that [`events::Events::find_first()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_first<Ev: crate::Event>(&self) -> Result<Option<Ev>, BasicError> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
self.find::<Ev>().next().transpose()
}
@@ -510,7 +450,7 @@ impl<T: Config, Evs: Decode> TransactionEvents<T, Evs> {
///
/// This works in the same way that [`events::Events::has()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn has<Ev: crate::Event>(&self) -> Result<bool, BasicError> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
-126
View File
@@ -1,126 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Perform runtime updates in the background using [UpdateClient].
//!
//! There are cases when the node would perform a runtime update. As a result, the subxt's metadata
//! would be out of sync and the API would not be able to submit valid extrinsics.
//! This API keeps the `RuntimeVersion` and `Metadata` of the client synced with the target node.
//!
//! The runtime update is recommended for long-running clients, or for cases where manually
//! restarting subxt would not be feasible. Even with this, extrinsics submitted during a node
//! runtime update are at risk or failing, as it will take `subxt` a moment to catch up.
//!
//! ## Note
//!
//! Here we use tokio to check for updates in the background, but any runtime can be used.
//!
//! ```no_run
//! # use subxt::{ClientBuilder, DefaultConfig};
//! #
//! # #[tokio::main]
//! # async fn main() {
//! # let client = ClientBuilder::new()
//! # .set_url("wss://rpc.polkadot.io:443")
//! # .build::<DefaultConfig>()
//! # .await
//! # .unwrap();
//! #
//! let update_client = client.updates();
//! // Spawn a new background task to handle runtime updates.
//! tokio::spawn(async move {
//! let result = update_client.perform_runtime_updates().await;
//! println!("Runtime update finished with result={:?}", result);
//! });
//! # }
//! ```
use crate::{
rpc::{
Rpc,
RuntimeVersion,
},
BasicError,
Config,
Metadata,
};
use parking_lot::RwLock;
use std::sync::Arc;
/// Client wrapper for performing runtime updates.
pub struct UpdateClient<T: Config> {
rpc: Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
runtime_version: Arc<RwLock<RuntimeVersion>>,
}
impl<T: Config> UpdateClient<T> {
/// Create a new [`UpdateClient`].
pub fn new(
rpc: Rpc<T>,
metadata: Arc<RwLock<Metadata>>,
runtime_version: Arc<RwLock<RuntimeVersion>>,
) -> Self {
Self {
rpc,
metadata,
runtime_version,
}
}
/// Performs runtime updates indefinitely unless encountering an error.
///
/// *Note:* This should be called from a dedicated background task.
pub async fn perform_runtime_updates(&self) -> Result<(), BasicError> {
// Obtain an update subscription to further detect changes in the runtime version of the node.
let mut update_subscription = self.rpc.subscribe_runtime_version().await?;
while let Some(update_runtime_version) = update_subscription.next().await {
// The Runtime Version obtained via subscription.
let update_runtime_version = update_runtime_version?;
// To ensure there are no races between:
// - starting the subxt::Client (fetching runtime version / metadata)
// - subscribing to the runtime updates
// the node provides its runtime version immediately after subscribing.
//
// In those cases, set the Runtime Version on the client if and only if
// the provided runtime version is different than what the client currently
// has stored.
{
// The Runtime Version of the client, as set during building the client
// or during updates.
let runtime_version = self.runtime_version.read();
if runtime_version.spec_version == update_runtime_version.spec_version {
tracing::debug!(
"Runtime update not performed for spec_version={}, client has spec_version={}",
update_runtime_version.spec_version, runtime_version.spec_version
);
continue
}
}
// Update the RuntimeVersion first.
{
let mut runtime_version = self.runtime_version.write();
// Update both the `RuntimeVersion` and `Metadata` of the client.
tracing::info!(
"Performing runtime update from {} to {}",
runtime_version.spec_version,
update_runtime_version.spec_version,
);
*runtime_version = update_runtime_version;
}
// Fetch the new metadata of the runtime node.
let update_metadata = self.rpc.metadata().await?;
tracing::debug!("Performing metadata update");
let mut metadata = self.metadata.write();
*metadata = update_metadata;
tracing::debug!("Runtime update completed");
}
Ok(())
}
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Miscellaneous utility helpers.
use codec::{
Decode,
DecodeAll,
Encode,
};
use derivative::Derivative;
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Encoded(pub Vec<u8>);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec<u8> {
self.0.to_owned()
}
}
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
///
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
/// access the real type `T` [`Self::try_decode`] needs to be used.
#[derive(Derivative, Encode, Decode)]
#[derivative(
Debug(bound = ""),
Clone(bound = ""),
PartialEq(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T: Decode> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T> {
T::decode_all(&mut &self.data[..]).ok()
}
/// Returns the length of the encoded `T`.
pub fn encoded_len(&self) -> usize {
self.data.len()
}
/// Returns the encoded data.
pub fn encoded(&self) -> &[u8] {
&self.data
}
/// Create from the given encoded `data`.
pub fn from_encoded(data: Vec<u8>) -> Self {
Self {
data,
_phantom: PhantomDataSendSync::new(),
}
}
}
/// A version of [`std::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).
#[derive(Derivative, Encode, Decode, scale_info::TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
Default(bound = ""),
Hash(bound = "")
)]
#[scale_info(skip_type_params(T))]
#[doc(hidden)]
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
impl<T> PhantomDataSendSync<T> {
pub(crate) fn new() -> Self {
Self(core::marker::PhantomData)
}
}
unsafe impl<T> Send for PhantomDataSendSync<T> {}
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// This represents a key-value collection and is SCALE compatible
/// with collections like BTreeMap. This has the same type params
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
+142 -47
View File
@@ -3,24 +3,29 @@
// see LICENSE for license details.
use crate::{
test_node_process,
test_node_process_with,
utils::node_runtime::system,
pair_signer,
test_context,
test_context_with,
utils::{
node_runtime,
wait_for_blocks,
},
};
use sp_core::storage::{
well_known_keys,
StorageKey,
use sp_core::{
sr25519::Pair as Sr25519Pair,
storage::well_known_keys,
Pair,
};
use sp_keyring::AccountKeyring;
use subxt::error::DispatchError;
#[tokio::test]
async fn insert_key() {
let test_node_process = test_node_process_with(AccountKeyring::Bob).await;
let client = test_node_process.client();
let ctx = test_context_with(AccountKeyring::Bob).await;
let api = ctx.client();
let public = AccountKeyring::Alice.public().as_array_ref().to_vec();
client
.rpc()
api.rpc()
.insert_key(
"aura".to_string(),
"//Alice".to_string(),
@@ -28,7 +33,7 @@ async fn insert_key() {
)
.await
.unwrap();
assert!(client
assert!(api
.rpc()
.has_key(public.clone().into(), "aura".to_string())
.await
@@ -37,29 +42,30 @@ async fn insert_key() {
#[tokio::test]
async fn fetch_block_hash() {
let node_process = test_node_process().await;
node_process.client().rpc().block_hash(None).await.unwrap();
let ctx = test_context().await;
ctx.client().rpc().block_hash(None).await.unwrap();
}
#[tokio::test]
async fn fetch_block() {
let node_process = test_node_process().await;
let client = node_process.client();
let block_hash = client.rpc().block_hash(None).await.unwrap();
client.rpc().block(block_hash).await.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let block_hash = api.rpc().block_hash(None).await.unwrap();
api.rpc().block(block_hash).await.unwrap();
}
#[tokio::test]
async fn fetch_read_proof() {
let node_process = test_node_process().await;
let client = node_process.client();
let block_hash = client.rpc().block_hash(None).await.unwrap();
client
.rpc()
let ctx = test_context().await;
let api = ctx.client();
let block_hash = api.rpc().block_hash(None).await.unwrap();
api.rpc()
.read_proof(
vec![
StorageKey(well_known_keys::HEAP_PAGES.to_vec()),
StorageKey(well_known_keys::EXTRINSIC_INDEX.to_vec()),
well_known_keys::HEAP_PAGES,
well_known_keys::EXTRINSIC_INDEX,
],
block_hash,
)
@@ -69,27 +75,31 @@ async fn fetch_read_proof() {
#[tokio::test]
async fn chain_subscribe_blocks() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut blocks = client.rpc().subscribe_blocks().await.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_blocks().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn chain_subscribe_finalized_blocks() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut blocks = client.rpc().subscribe_finalized_blocks().await.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let mut blocks = api.rpc().subscribe_finalized_blocks().await.unwrap();
blocks.next().await.unwrap().unwrap();
}
#[tokio::test]
async fn fetch_keys() {
let node_process = test_node_process().await;
let client = node_process.client();
let keys = client
let ctx = test_context().await;
let api = ctx.client();
let addr = node_runtime::storage().system().account_root();
let keys = api
.storage()
.fetch_keys::<system::storage::Account>(4, None, None)
.fetch_keys(&addr.to_root_bytes(), 4, None, None)
.await
.unwrap();
assert_eq!(keys.len(), 4)
@@ -97,13 +107,11 @@ async fn fetch_keys() {
#[tokio::test]
async fn test_iter() {
let node_process = test_node_process().await;
let client = node_process.client();
let mut iter = client
.storage()
.iter::<system::storage::Account>(None)
.await
.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let addr = node_runtime::storage().system().account_root();
let mut iter = api.storage().iter(addr, 10, None).await.unwrap();
let mut i = 0;
while iter.next().await.unwrap().is_some() {
i += 1;
@@ -113,9 +121,96 @@ async fn test_iter() {
#[tokio::test]
async fn fetch_system_info() {
let node_process = test_node_process().await;
let client = node_process.client();
assert_eq!(client.rpc().system_chain().await.unwrap(), "Development");
assert_eq!(client.rpc().system_name().await.unwrap(), "Substrate Node");
assert!(!client.rpc().system_version().await.unwrap().is_empty());
let ctx = test_context().await;
let api = ctx.client();
assert_eq!(api.rpc().system_chain().await.unwrap(), "Development");
assert_eq!(api.rpc().system_name().await.unwrap(), "Substrate Node");
assert!(!api.rpc().system_version().await.unwrap().is_empty());
}
#[tokio::test]
async fn dry_run_passes() {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
wait_for_blocks(&api).await;
let tx = node_runtime::tx()
.balances()
.transfer(bob.account_id().clone().into(), 10_000);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();
api.rpc()
.dry_run(signed_extrinsic.encoded(), None)
.await
.expect("dryrunning failed")
.expect("expected dryrunning to be successful")
.unwrap();
signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized_success()
.await
.unwrap();
}
#[tokio::test]
async fn dry_run_fails() {
let ctx = test_context().await;
let api = ctx.client();
wait_for_blocks(&api).await;
let alice = pair_signer(AccountKeyring::Alice.pair());
let hans = pair_signer(Sr25519Pair::generate().0);
let tx = node_runtime::tx().balances().transfer(
hans.account_id().clone().into(),
100_000_000_000_000_000_000_000_000_000_000_000,
);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();
let dry_run_res = api
.rpc()
.dry_run(signed_extrinsic.encoded(), None)
.await
.expect("dryrunning failed")
.expect("expected dryrun transaction to be valid");
if let Err(sp_runtime::DispatchError::Module(module_error)) = dry_run_res {
assert_eq!(module_error.index, 6);
assert_eq!(module_error.error, 2);
} else {
panic!("expected a module error when dryrunning");
}
let res = signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized_success()
.await;
if let Err(subxt::error::Error::Runtime(DispatchError::Module(err))) = res {
assert_eq!(err.pallet, "Balances");
assert_eq!(err.error, "InsufficientBalance");
} else {
panic!("expected a runtime module error");
}
}
File diff suppressed because it is too large Load Diff
+36 -34
View File
@@ -4,28 +4,30 @@
use crate::{
node_runtime::{
self,
balances,
system,
},
pair_signer,
test_context,
utils::wait_for_blocks,
};
use futures::StreamExt;
use sp_keyring::AccountKeyring;
// Check that we can subscribe to non-finalized block events.
#[tokio::test]
async fn non_finalized_block_subscription() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn non_finalized_block_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = ctx.api.events().subscribe().await?;
let mut event_sub = api.events().subscribe().await?;
// Wait for the next set of events, and check that the
// associated block hash is not finalized yet.
let events = event_sub.next().await.unwrap()?;
let event_block_hash = events.block_hash();
let current_block_hash = ctx.api.client.rpc().block_hash(None).await?.unwrap();
let current_block_hash = api.rpc().block_hash(None).await?.unwrap();
assert_eq!(event_block_hash, current_block_hash);
Ok(())
@@ -33,18 +35,18 @@ async fn non_finalized_block_subscription() -> Result<(), subxt::BasicError> {
// Check that we can subscribe to finalized block events.
#[tokio::test]
async fn finalized_block_subscription() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn finalized_block_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = ctx.api.events().subscribe_finalized().await?;
let mut event_sub = api.events().subscribe_finalized().await?;
// Wait for the next set of events, and check that the
// associated block hash is the one we just finalized.
// (this can be a bit slow as we have to wait for finalization)
let events = event_sub.next().await.unwrap()?;
let event_block_hash = events.block_hash();
let finalized_hash = ctx.api.client.rpc().finalized_head().await?;
let finalized_hash = api.rpc().finalized_head().await?;
assert_eq!(event_block_hash, finalized_hash);
Ok(())
@@ -53,24 +55,25 @@ async fn finalized_block_subscription() -> Result<(), subxt::BasicError> {
// Check that our subscription actually keeps producing events for
// a few blocks.
#[tokio::test]
async fn subscription_produces_events_each_block() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn subscription_produces_events_each_block() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut event_sub = ctx.api.events().subscribe().await?;
wait_for_blocks(&api).await;
let mut event_sub = api.events().subscribe().await?;
for i in 0..3 {
let events = event_sub
.next()
.await
.expect("events expected each block")?;
let success_event = events
.find_first::<system::events::ExtrinsicSuccess>()
.expect("decode error");
// Every now and then I get no bytes back for the first block events;
// I assume that this might be the case for the genesis block, so don't
// worry if no event found (but we should have no decode errors etc either way).
if i > 0 && success_event.is_none() {
if success_event.is_none() {
let n = events.len();
panic!("Expected an extrinsic success event on iteration {i} (saw {n} other events)")
}
@@ -82,13 +85,12 @@ async fn subscription_produces_events_each_block() -> Result<(), subxt::BasicErr
// Check that our subscription receives events, and we can filter them based on
// it's Stream impl, and ultimately see the event we expect.
#[tokio::test]
async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
tracing_subscriber::fmt::try_init().ok();
async fn balance_transfer_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Subscribe to balance transfer events, ignoring all else.
let event_sub = ctx
.api
let event_sub = api
.events()
.subscribe()
.await?
@@ -101,11 +103,12 @@ async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
// Make a transfer:
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
ctx.api
.tx()
let transfer_tx = node_runtime::tx()
.balances()
.transfer(bob.clone().into(), 10_000)?
.sign_and_submit_then_watch_default(&alice)
.transfer(bob.clone().into(), 10_000);
api.tx()
.sign_and_submit_then_watch_default(&transfer_tx, &alice)
.await?;
// Wait for the next balance transfer event in our subscription stream
@@ -124,21 +127,20 @@ async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
}
#[tokio::test]
async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::BasicError> {
async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// This function is not publically available to use, but contains
// the key logic for filling in missing blocks, so we want to test it.
// This is used in `subscribe_finalized` to ensure no block headers are
// missed.
use subxt::events::subscribe_to_block_headers_filling_in_gaps;
let ctx = test_context().await;
// Manually subscribe to the next 6 finalized block headers, but deliberately
// filter out some in the middle so we get back b _ _ b _ b. This guarantees
// that there will be some gaps, even if there aren't any from the subscription.
let some_finalized_blocks = ctx
.api
.client
let some_finalized_blocks = api
.rpc()
.subscribe_finalized_blocks()
.await?
@@ -152,7 +154,7 @@ async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::BasicErr
// This should spot any gaps in the middle and fill them back in.
let all_finalized_blocks = subscribe_to_block_headers_filling_in_gaps(
&ctx.api.client,
ctx.client(),
None,
some_finalized_blocks,
);
@@ -185,7 +187,7 @@ async fn check_events_are_sendable() {
tokio::task::spawn(async {
let ctx = test_context().await;
let mut event_sub = ctx.api.events().subscribe().await?;
let mut event_sub = ctx.client().events().subscribe().await?;
while let Some(ev) = event_sub.next().await {
// if `event_sub` doesn't implement Send, we can't hold
@@ -193,7 +195,7 @@ async fn check_events_are_sendable() {
// requires Send. This will lead to a compile error.
}
Ok::<_, subxt::BasicError>(())
Ok::<_, subxt::Error>(())
});
// Check that FilterEvents can be used across await points.
@@ -201,7 +203,7 @@ async fn check_events_are_sendable() {
let ctx = test_context().await;
let mut event_sub = ctx
.api
.client()
.events()
.subscribe()
.await?
@@ -213,6 +215,6 @@ async fn check_events_are_sendable() {
// requires Send; This will lead to a compile error.
}
Ok::<_, subxt::BasicError>(())
Ok::<_, subxt::Error>(())
});
}
+169 -84
View File
@@ -4,10 +4,10 @@
use crate::{
node_runtime::{
self,
balances,
runtime_types,
system,
DispatchError,
},
pair_signer,
test_context,
@@ -22,32 +22,36 @@ use sp_runtime::{
AccountId32,
MultiAddress,
};
use subxt::Error;
use subxt::error::{
DispatchError,
Error,
};
#[tokio::test]
async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
async fn tx_basic_transfer() -> Result<(), subxt::Error> {
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let bob_address = bob.account_id().clone().into();
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
let alice_account_addr = node_runtime::storage().system().account(alice.account_id());
let bob_account_addr = node_runtime::storage().system().account(bob.account_id());
let alice_pre = api
.storage()
.system()
.account(alice.account_id(), None)
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_pre = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
let tx = node_runtime::tx().balances().transfer(bob_address, 10_000);
let events = api
.tx()
.balances()
.transfer(bob_address, 10_000)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?;
@@ -69,13 +73,11 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
let alice_post = api
.storage()
.system()
.account(alice.account_id(), None)
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_post = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
assert!(alice_pre.data.free - 10_000 >= alice_post.data.free);
@@ -84,26 +86,118 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
}
#[tokio::test]
async fn multiple_transfers_work_nonce_incremented(
) -> Result<(), subxt::Error<DispatchError>> {
async fn tx_dynamic_transfer() -> Result<(), subxt::Error> {
use subxt::ext::scale_value::{
At,
Composite,
Value,
};
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let ctx = test_context().await;
let api = ctx.client();
let alice_account_addr = subxt::dynamic::storage(
"System",
"Account",
vec![Value::from_bytes(&alice.account_id())],
);
let bob_account_addr = subxt::dynamic::storage(
"System",
"Account",
vec![Value::from_bytes(&bob.account_id())],
);
let alice_pre = api
.storage()
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_pre = api
.storage()
.fetch_or_default(&bob_account_addr, None)
.await?;
let tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
Value::unnamed_variant("Id", vec![Value::from_bytes(&bob.account_id())]),
Value::u128(10_000u128),
],
);
let events = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?;
let event_fields = events
.iter()
.filter_map(|ev| ev.ok())
.find(|ev| ev.pallet_name() == "Balances" && ev.variant_name() == "Transfer")
.expect("Failed to find Transfer event")
.field_values()
.map_context(|_| ());
let expected_fields = Composite::Named(vec![
(
"from".into(),
Value::unnamed_composite(vec![Value::from_bytes(&alice.account_id())]),
),
(
"to".into(),
Value::unnamed_composite(vec![Value::from_bytes(&bob.account_id())]),
),
("amount".into(), Value::u128(10_000)),
]);
assert_eq!(event_fields, expected_fields);
let alice_post = api
.storage()
.fetch_or_default(&alice_account_addr, None)
.await?;
let bob_post = api
.storage()
.fetch_or_default(&bob_account_addr, None)
.await?;
let alice_pre_free = alice_pre.at("data").at("free").unwrap().as_u128().unwrap();
let alice_post_free = alice_post.at("data").at("free").unwrap().as_u128().unwrap();
let bob_pre_free = bob_pre.at("data").at("free").unwrap().as_u128().unwrap();
let bob_post_free = bob_post.at("data").at("free").unwrap().as_u128().unwrap();
assert!(alice_pre_free - 10_000 >= alice_post_free);
assert_eq!(bob_pre_free + 10_000, bob_post_free);
Ok(())
}
#[tokio::test]
async fn multiple_transfers_work_nonce_incremented() -> Result<(), subxt::Error> {
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let bob_address: MultiAddress<AccountId32, u32> = bob.account_id().clone().into();
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
let bob_account_addr = node_runtime::storage().system().account(bob.account_id());
let bob_pre = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
let tx = node_runtime::tx()
.balances()
.transfer(bob_address.clone(), 10_000);
for _ in 0..3 {
api
.tx()
.balances()
.transfer(bob_address.clone(), 10_000)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_in_block() // Don't need to wait for finalization; this is quicker.
.await?
@@ -113,8 +207,7 @@ async fn multiple_transfers_work_nonce_incremented(
let bob_post = api
.storage()
.system()
.account(bob.account_id(), None)
.fetch_or_default(&bob_account_addr, None)
.await?;
assert_eq!(bob_pre.data.free + 30_000, bob_post.data.free);
@@ -123,45 +216,40 @@ async fn multiple_transfers_work_nonce_incremented(
#[tokio::test]
async fn storage_total_issuance() {
let cxt = test_context().await;
let total_issuance = cxt
.api
.storage()
.balances()
.total_issuance(None)
.await
.unwrap();
let ctx = test_context().await;
let api = ctx.client();
let addr = node_runtime::storage().balances().total_issuance();
let total_issuance = api.storage().fetch_or_default(&addr, None).await.unwrap();
assert_ne!(total_issuance, 0);
}
#[tokio::test]
async fn storage_balance_lock() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_balance_lock() -> Result<(), subxt::Error> {
let bob = pair_signer(AccountKeyring::Bob.pair());
let charlie = AccountKeyring::Charlie.to_account_id();
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
cxt.api
.tx()
.staking()
.bond(
charlie.into(),
100_000_000_000_000,
runtime_types::pallet_staking::RewardDestination::Stash,
)?
.sign_and_submit_then_watch_default(&bob)
let tx = node_runtime::tx().staking().bond(
charlie.into(),
100_000_000_000_000,
runtime_types::pallet_staking::RewardDestination::Stash,
);
api.tx()
.sign_and_submit_then_watch_default(&tx, &bob)
.await?
.wait_for_finalized_success()
.await?
.find_first::<system::events::ExtrinsicSuccess>()?
.expect("No ExtrinsicSuccess Event found");
let locked_account = AccountKeyring::Bob.to_account_id();
let locks = cxt
.api
.storage()
let locks_addr = node_runtime::storage()
.balances()
.locks(&locked_account, None)
.await?;
.locks(&AccountKeyring::Bob.to_account_id());
let locks = api.storage().fetch_or_default(&locks_addr, None).await?;
assert_eq!(
locks.0,
@@ -177,38 +265,37 @@ async fn storage_balance_lock() -> Result<(), subxt::Error<DispatchError>> {
#[tokio::test]
async fn transfer_error() {
tracing_subscriber::fmt::try_init().ok();
let alice = pair_signer(AccountKeyring::Alice.pair());
let alice_addr = alice.account_id().clone().into();
let hans = pair_signer(Pair::generate().0);
let hans_address = hans.account_id().clone().into();
let ctx = test_context().await;
let api = ctx.client();
ctx.api
.tx()
let to_hans_tx = node_runtime::tx()
.balances()
.transfer(hans_address, 100_000_000_000_000_000)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.transfer(hans_address, 100_000_000_000_000_000);
let to_alice_tx = node_runtime::tx()
.balances()
.transfer(alice_addr, 100_000_000_000_000_000);
api.tx()
.sign_and_submit_then_watch_default(&to_hans_tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
.await
.unwrap();
let res = ctx
.api
let res = api
.tx()
.balances()
.transfer(alice_addr, 100_000_000_000_000_000)
.unwrap()
.sign_and_submit_then_watch_default(&hans)
.sign_and_submit_then_watch_default(&to_alice_tx, &hans)
.await
.unwrap()
.wait_for_finalized_success()
.await;
if let Err(Error::Module(err)) = res {
if let Err(Error::Runtime(DispatchError::Module(err))) = res {
assert_eq!(err.pallet, "Balances");
assert_eq!(err.error, "InsufficientBalance");
} else {
@@ -218,19 +305,17 @@ async fn transfer_error() {
#[tokio::test]
async fn transfer_implicit_subscription() {
tracing_subscriber::fmt::try_init().ok();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
let bob_addr = bob.clone().into();
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let event = cxt
.api
let to_bob_tx = node_runtime::tx().balances().transfer(bob_addr, 10_000);
let event = api
.tx()
.balances()
.transfer(bob_addr, 10_000)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&to_bob_tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -252,19 +337,19 @@ async fn transfer_implicit_subscription() {
#[tokio::test]
async fn constant_existential_deposit() {
let cxt = test_context().await;
let locked_metadata = cxt.client().metadata();
let metadata = locked_metadata.read();
let ctx = test_context().await;
let api = ctx.client();
// get and decode constant manually via metadata:
let metadata = api.metadata();
let balances_metadata = metadata.pallet("Balances").unwrap();
let constant_metadata = balances_metadata.constant("ExistentialDeposit").unwrap();
let existential_deposit = u128::decode(&mut &constant_metadata.value[..]).unwrap();
assert_eq!(existential_deposit, 100_000_000_000_000);
assert_eq!(
existential_deposit,
cxt.api
.constants()
.balances()
.existential_deposit()
.unwrap()
);
// constant address for API access:
let addr = node_runtime::constants().balances().existential_deposit();
// Make sure thetwo are identical:
assert_eq!(existential_deposit, api.constants().at(&addr).unwrap());
}
@@ -7,57 +7,46 @@ use sp_keyring::AccountKeyring;
use crate::{
node_runtime::{
self,
contracts::{
calls::TransactionApi,
events,
storage,
},
contracts::events,
system,
DispatchError,
},
test_context,
NodeRuntimeParams,
TestContext,
};
use sp_core::sr25519::Pair;
use sp_runtime::MultiAddress;
use subxt::{
Client,
tx::{
PairSigner,
TxProgress,
},
Config,
DefaultConfig,
Error,
PairSigner,
TransactionProgress,
OnlineClient,
SubstrateConfig,
};
struct ContractsTestContext {
cxt: TestContext,
signer: PairSigner<DefaultConfig, Pair>,
signer: PairSigner<SubstrateConfig, Pair>,
}
type Hash = <DefaultConfig as Config>::Hash;
type AccountId = <DefaultConfig as Config>::AccountId;
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
impl ContractsTestContext {
async fn init() -> Self {
tracing_subscriber::fmt::try_init().ok();
let cxt = test_context().await;
let signer = PairSigner::new(AccountKeyring::Alice.pair());
Self { cxt, signer }
}
fn client(&self) -> &Client<DefaultConfig> {
fn client(&self) -> OnlineClient<SubstrateConfig> {
self.cxt.client()
}
fn contracts_tx(&self) -> TransactionApi<DefaultConfig, NodeRuntimeParams> {
self.cxt.api.tx().contracts()
}
async fn instantiate_with_code(
&self,
) -> Result<(Hash, AccountId), Error<DispatchError>> {
async fn instantiate_with_code(&self) -> Result<(Hash, AccountId), Error> {
tracing::info!("instantiate_with_code:");
const CONTRACT: &str = r#"
(module
@@ -67,20 +56,19 @@ impl ContractsTestContext {
"#;
let code = wabt::wat2wasm(CONTRACT).expect("invalid wabt");
let instantiate_tx = node_runtime::tx().contracts().instantiate_with_code(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code,
vec![], // data
vec![], // salt
);
let events = self
.cxt
.api
.client()
.tx()
.contracts()
.instantiate_with_code(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code,
vec![], // data
vec![], // salt
)?
.sign_and_submit_then_watch_default(&self.signer)
.sign_and_submit_then_watch_default(&instantiate_tx, &self.signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -108,19 +96,21 @@ impl ContractsTestContext {
code_hash: Hash,
data: Vec<u8>,
salt: Vec<u8>,
) -> Result<AccountId, Error<DispatchError>> {
) -> Result<AccountId, Error> {
// call instantiate extrinsic
let instantiate_tx = node_runtime::tx().contracts().instantiate(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code_hash,
data,
salt,
);
let result = self
.contracts_tx()
.instantiate(
100_000_000_000_000_000, // endowment
500_000_000_000, // gas_limit
None, // storage_deposit_limit
code_hash,
data,
salt,
)?
.sign_and_submit_then_watch_default(&self.signer)
.client()
.tx()
.sign_and_submit_then_watch_default(&instantiate_tx, &self.signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -137,21 +127,20 @@ impl ContractsTestContext {
&self,
contract: AccountId,
input_data: Vec<u8>,
) -> Result<
TransactionProgress<'_, DefaultConfig, DispatchError, node_runtime::Event>,
Error<DispatchError>,
> {
) -> Result<TxProgress<SubstrateConfig, OnlineClient<SubstrateConfig>>, Error> {
tracing::info!("call: {:?}", contract);
let call_tx = node_runtime::tx().contracts().call(
MultiAddress::Id(contract),
0, // value
500_000_000, // gas_limit
None, // storage_deposit_limit
input_data,
);
let result = self
.contracts_tx()
.call(
MultiAddress::Id(contract),
0, // value
500_000_000, // gas_limit
None, // storage_deposit_limit
input_data,
)?
.sign_and_submit_then_watch_default(&self.signer)
.client()
.tx()
.sign_and_submit_then_watch_default(&call_tx, &self.signer)
.await?;
tracing::info!("Call result: {:?}", result);
@@ -190,19 +179,17 @@ async fn tx_call() {
let cxt = ContractsTestContext::init().await;
let (_, contract) = cxt.instantiate_with_code().await.unwrap();
let contract_info = cxt
.cxt
.api
.storage()
let info_addr = node_runtime::storage()
.contracts()
.contract_info_of(&contract, None)
.await;
.contract_info_of(&contract);
let contract_info = cxt.client().storage().fetch(&info_addr, None).await;
assert!(contract_info.is_ok());
let keys = cxt
.client()
.storage()
.fetch_keys::<storage::ContractInfoOf>(5, None, None)
.fetch_keys(&info_addr.to_bytes(), 10, None, None)
.await
.unwrap()
.iter()
+96 -93
View File
@@ -4,12 +4,12 @@
use crate::{
node_runtime::{
self,
runtime_types::pallet_staking::{
RewardDestination,
ValidatorPrefs,
},
staking,
DispatchError,
},
pair_signer,
test_context,
@@ -20,7 +20,10 @@ use sp_core::{
Pair,
};
use sp_keyring::AccountKeyring;
use subxt::Error;
use subxt::error::{
DispatchError,
Error,
};
/// Helper function to generate a crypto pair from seed
fn get_from_seed(seed: &str) -> sr25519::Pair {
@@ -37,14 +40,17 @@ fn default_validator_prefs() -> ValidatorPrefs {
#[tokio::test]
async fn validate_with_controller_account() {
let alice = pair_signer(AccountKeyring::Alice.pair());
let ctx = test_context().await;
ctx.api
.tx()
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs())
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.validate(default_validator_prefs());
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -53,19 +59,23 @@ async fn validate_with_controller_account() {
}
#[tokio::test]
async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchError>> {
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
async fn validate_not_possible_for_stash_account() -> Result<(), Error> {
let ctx = test_context().await;
let announce_validator = ctx
.api
.tx()
let api = ctx.client();
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
let tx = node_runtime::tx()
.staking()
.validate(default_validator_prefs())?
.sign_and_submit_then_watch_default(&alice_stash)
.validate(default_validator_prefs());
let announce_validator = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.await?
.wait_for_finalized_success()
.await;
assert_matches!(announce_validator, Err(Error::Module(err)) => {
assert_matches!(announce_validator, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "NotController");
});
@@ -74,16 +84,18 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
#[tokio::test]
async fn nominate_with_controller_account() {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = pair_signer(AccountKeyring::Bob.pair());
let ctx = test_context().await;
ctx.api
.tx()
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.account_id().clone().into()])
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.nominate(vec![bob.account_id().clone().into()]);
api.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -92,22 +104,26 @@ async fn nominate_with_controller_account() {
}
#[tokio::test]
async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchError>> {
async fn nominate_not_possible_for_stash_account() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
let bob = pair_signer(AccountKeyring::Bob.pair());
let ctx = test_context().await;
let nomination = ctx
.api
.tx()
let tx = node_runtime::tx()
.staking()
.nominate(vec![bob.account_id().clone().into()])?
.sign_and_submit_then_watch_default(&alice_stash)
.await?
.nominate(vec![bob.account_id().clone().into()]);
let nomination = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice_stash)
.await
.unwrap()
.wait_for_finalized_success()
.await;
assert_matches!(nomination, Err(Error::Module(err)) => {
assert_matches!(nomination, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "NotController");
});
@@ -115,52 +131,45 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
}
#[tokio::test]
async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
async fn chill_works_for_controller_only() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
let bob_stash = pair_signer(get_from_seed("Bob//stash"));
let alice = pair_signer(AccountKeyring::Alice.pair());
let ctx = test_context().await;
// this will fail the second time, which is why this is one test, not two
ctx.api
.tx()
let nominate_tx = node_runtime::tx()
.staking()
.nominate(vec![bob_stash.account_id().clone().into()])?
.sign_and_submit_then_watch_default(&alice)
.nominate(vec![bob_stash.account_id().clone().into()]);
api.tx()
.sign_and_submit_then_watch_default(&nominate_tx, &alice)
.await?
.wait_for_finalized_success()
.await?;
let ledger = ctx
.api
.storage()
.staking()
.ledger(alice.account_id(), None)
.await?
.unwrap();
let ledger_addr = node_runtime::storage().staking().ledger(alice.account_id());
let ledger = api.storage().fetch(&ledger_addr, None).await?.unwrap();
assert_eq!(alice_stash.account_id(), &ledger.stash);
let chill = ctx
.api
let chill_tx = node_runtime::tx().staking().chill();
let chill = api
.tx()
.staking()
.chill()?
.sign_and_submit_then_watch_default(&alice_stash)
.sign_and_submit_then_watch_default(&chill_tx, &alice_stash)
.await?
.wait_for_finalized_success()
.await;
assert_matches!(chill, Err(Error::Module(err)) => {
assert_matches!(chill, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "NotController");
});
let is_chilled = ctx
.api
let is_chilled = api
.tx()
.staking()
.chill()?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&chill_tx, &alice)
.await?
.wait_for_finalized_success()
.await?
@@ -171,43 +180,35 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
}
#[tokio::test]
async fn tx_bond() -> Result<(), Error<DispatchError>> {
let alice = pair_signer(AccountKeyring::Alice.pair());
async fn tx_bond() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let bond = ctx
.api
let alice = pair_signer(AccountKeyring::Alice.pair());
let bond_tx = node_runtime::tx().staking().bond(
AccountKeyring::Bob.to_account_id().into(),
100_000_000_000_000,
RewardDestination::Stash,
);
let bond = api
.tx()
.staking()
.bond(
AccountKeyring::Bob.to_account_id().into(),
100_000_000_000_000,
RewardDestination::Stash,
)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&bond_tx, &alice)
.await?
.wait_for_finalized_success()
.await;
assert!(bond.is_ok());
let bond_again = ctx
.api
let bond_again = api
.tx()
.staking()
.bond(
AccountKeyring::Bob.to_account_id().into(),
100_000_000_000_000,
RewardDestination::Stash,
)
.unwrap()
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&bond_tx, &alice)
.await?
.wait_for_finalized_success()
.await;
assert_matches!(bond_again, Err(Error::Module(err)) => {
assert_matches!(bond_again, Err(Error::Runtime(DispatchError::Module(err))) => {
assert_eq!(err.pallet, "Staking");
assert_eq!(err.error, "AlreadyBonded");
});
@@ -215,35 +216,37 @@ async fn tx_bond() -> Result<(), Error<DispatchError>> {
}
#[tokio::test]
async fn storage_history_depth() -> Result<(), Error<DispatchError>> {
async fn storage_history_depth() -> Result<(), Error> {
let ctx = test_context().await;
let history_depth = ctx.api.storage().staking().history_depth(None).await?;
let api = ctx.client();
let history_depth_addr = node_runtime::storage().staking().history_depth();
let history_depth = api
.storage()
.fetch_or_default(&history_depth_addr, None)
.await?;
assert_eq!(history_depth, 84);
Ok(())
}
#[tokio::test]
async fn storage_current_era() -> Result<(), Error<DispatchError>> {
async fn storage_current_era() -> Result<(), Error> {
let ctx = test_context().await;
let _current_era = ctx
.api
let api = ctx.client();
let current_era_addr = node_runtime::storage().staking().current_era();
let _current_era = api
.storage()
.staking()
.current_era(None)
.fetch(&current_era_addr, None)
.await?
.expect("current era always exists");
Ok(())
}
#[tokio::test]
async fn storage_era_reward_points() -> Result<(), Error<DispatchError>> {
let cxt = test_context().await;
let current_era_result = cxt
.api
.storage()
.staking()
.eras_reward_points(&0, None)
.await;
async fn storage_era_reward_points() -> Result<(), Error> {
let ctx = test_context().await;
let api = ctx.client();
let reward_points_addr = node_runtime::storage().staking().eras_reward_points(&0);
let current_era_result = api.storage().fetch(&reward_points_addr, None).await;
assert!(current_era_result.is_ok());
Ok(())
+16 -16
View File
@@ -4,35 +4,35 @@
use crate::{
node_runtime::{
self,
runtime_types,
sudo,
DispatchError,
},
pair_signer,
test_context,
};
use sp_keyring::AccountKeyring;
type Call = runtime_types::node_runtime::Call;
type Call = runtime_types::kitchensink_runtime::Call;
type BalancesCall = runtime_types::pallet_balances::pallet::Call;
#[tokio::test]
async fn test_sudo() -> Result<(), subxt::Error<DispatchError>> {
async fn test_sudo() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id().into();
let cxt = test_context().await;
let call = Call::Balances(BalancesCall::transfer {
dest: bob,
value: 10_000,
});
let tx = node_runtime::tx().sudo().sudo(call);
let found_event = cxt
.api
let found_event = api
.tx()
.sudo()
.sudo(call)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?
@@ -43,22 +43,22 @@ async fn test_sudo() -> Result<(), subxt::Error<DispatchError>> {
}
#[tokio::test]
async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error<DispatchError>> {
async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id().into();
let cxt = test_context().await;
let call = Call::Balances(BalancesCall::transfer {
dest: bob,
value: 10_000,
});
let tx = node_runtime::tx().sudo().sudo_unchecked_weight(call, 0);
let found_event = cxt
.api
let found_event = api
.tx()
.sudo()
.sudo_unchecked_weight(call, 0)?
.sign_and_submit_then_watch_default(&alice)
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?
+20 -15
View File
@@ -4,8 +4,8 @@
use crate::{
node_runtime::{
self,
system,
DispatchError,
},
pair_signer,
test_context,
@@ -14,15 +14,17 @@ use assert_matches::assert_matches;
use sp_keyring::AccountKeyring;
#[tokio::test]
async fn storage_account() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_account() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let alice = pair_signer(AccountKeyring::Alice.pair());
let cxt = test_context().await;
let account_info = cxt
.api
let account_info_addr = node_runtime::storage().system().account(alice.account_id());
let account_info = api
.storage()
.system()
.account(alice.account_id(), None)
.fetch_or_default(&account_info_addr, None)
.await;
assert_matches!(account_info, Ok(_));
@@ -30,16 +32,19 @@ async fn storage_account() -> Result<(), subxt::Error<DispatchError>> {
}
#[tokio::test]
async fn tx_remark_with_event() -> Result<(), subxt::Error<DispatchError>> {
let alice = pair_signer(AccountKeyring::Alice.pair());
let cxt = test_context().await;
async fn tx_remark_with_event() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let found_event = cxt
.api
.tx()
let alice = pair_signer(AccountKeyring::Alice.pair());
let tx = node_runtime::tx()
.system()
.remark_with_event(b"remarkable".to_vec())?
.sign_and_submit_then_watch_default(&alice)
.remark_with_event(b"remarkable".to_vec());
let found_event = api
.tx()
.sign_and_submit_then_watch_default(&tx, &alice)
.await?
.wait_for_finalized_success()
.await?
@@ -2,13 +2,20 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::test_context;
use crate::{
node_runtime,
test_context,
};
#[tokio::test]
async fn storage_get_current_timestamp() {
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let timestamp = cxt.api.storage().timestamp().now(None).await;
let timestamp = api
.storage()
.fetch(&node_runtime::storage().timestamp().now(), None)
.await;
assert!(timestamp.is_ok())
}
+8
View File
@@ -24,3 +24,11 @@ mod storage;
use test_runtime::node_runtime;
#[cfg(test)]
use utils::*;
// We don't use this dependency, but it's here so that we
// can enable logging easily if need be. Add this to a test
// to enable tracing for it:
//
// tracing_subscriber::fmt::init();
#[cfg(test)]
use tracing_subscriber as _;
@@ -3,6 +3,7 @@
// see LICENSE for license details.
use crate::{
node_runtime,
test_context,
TestContext,
};
@@ -28,72 +29,57 @@ use scale_info::{
TypeInfo,
};
use subxt::{
ClientBuilder,
DefaultConfig,
Metadata,
SubstrateExtrinsicParams,
OfflineClient,
SubstrateConfig,
};
use crate::utils::node_runtime;
type RuntimeApi =
node_runtime::RuntimeApi<DefaultConfig, SubstrateExtrinsicParams<DefaultConfig>>;
async fn metadata_to_api(metadata: RuntimeMetadataV14, cxt: &TestContext) -> RuntimeApi {
async fn metadata_to_api(
metadata: RuntimeMetadataV14,
ctx: &TestContext,
) -> OfflineClient<SubstrateConfig> {
let prefixed = RuntimeMetadataPrefixed::from(metadata);
let metadata = Metadata::try_from(prefixed).unwrap();
ClientBuilder::new()
.set_url(cxt.node_proc.ws_url().to_string())
.set_metadata(metadata)
.build()
.await
.unwrap()
.to_runtime_api::<node_runtime::RuntimeApi<
DefaultConfig,
SubstrateExtrinsicParams<DefaultConfig>,
>>()
OfflineClient::new(
ctx.client().genesis_hash(),
ctx.client().runtime_version(),
metadata,
)
}
#[tokio::test]
async fn full_metadata_check() {
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
// Runtime metadata is identical to the metadata used during API generation.
assert!(api.validate_metadata().is_ok());
assert!(node_runtime::validate_codegen(&api).is_ok());
// Modify the metadata.
let mut metadata: RuntimeMetadataV14 = {
let locked_client_metadata = api.client.metadata();
let client_metadata = locked_client_metadata.read();
client_metadata.runtime_metadata().clone()
};
let mut metadata: RuntimeMetadataV14 = api.metadata().runtime_metadata().clone();
metadata.pallets[0].name = "NewPallet".to_string();
let new_api = metadata_to_api(metadata, &cxt).await;
let api = metadata_to_api(metadata, &ctx).await;
assert_eq!(
new_api
.validate_metadata()
node_runtime::validate_codegen(&api)
.expect_err("Validation should fail for incompatible metadata"),
::subxt::MetadataError::IncompatibleMetadata
::subxt::error::MetadataError::IncompatibleMetadata
);
}
#[tokio::test]
async fn constant_values_are_not_validated() {
let cxt = test_context().await;
let api = &cxt.api;
let ctx = test_context().await;
let api = ctx.client();
// Ensure that `ExistentialDeposit` is compatible before altering the metadata.
assert!(cxt.api.constants().balances().existential_deposit().is_ok());
let deposit_addr = node_runtime::constants().balances().existential_deposit();
// Retrieve existential deposit to validate it and confirm that it's OK.
assert!(api.constants().at(&deposit_addr).is_ok());
// Modify the metadata.
let mut metadata: RuntimeMetadataV14 = {
let locked_client_metadata = api.client.metadata();
let client_metadata = locked_client_metadata.read();
client_metadata.runtime_metadata().clone()
};
let mut metadata: RuntimeMetadataV14 = api.metadata().runtime_metadata().clone();
let mut existential = metadata
.pallets
@@ -108,13 +94,10 @@ async fn constant_values_are_not_validated() {
// Modifying a constant value should not lead to an error:
existential.value = vec![0u8; 32];
let new_api = metadata_to_api(metadata, &cxt).await;
let api = metadata_to_api(metadata, &ctx).await;
assert!(new_api.validate_metadata().is_ok());
assert!(new_api.constants().balances().existential_deposit().is_ok());
// Other constant validation should not be impacted.
assert!(new_api.constants().balances().max_locks().is_ok());
assert!(node_runtime::validate_codegen(&api).is_ok());
assert!(api.constants().at(&deposit_addr).is_ok());
}
fn default_pallet() -> PalletMetadata {
@@ -143,11 +126,15 @@ fn pallets_to_metadata(pallets: Vec<PalletMetadata>) -> RuntimeMetadataV14 {
#[tokio::test]
async fn calls_check() {
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let unbond_tx = node_runtime::tx().staking().unbond(123_456_789_012_345);
let withdraw_unbonded_addr = node_runtime::tx().staking().withdraw_unbonded(10);
// Ensure that `Unbond` and `WinthdrawUnbonded` calls are compatible before altering the metadata.
assert!(cxt.api.tx().staking().unbond(123_456_789_012_345).is_ok());
assert!(cxt.api.tx().staking().withdraw_unbonded(10).is_ok());
assert!(api.tx().validate(&unbond_tx).is_ok());
assert!(api.tx().validate(&withdraw_unbonded_addr).is_ok());
// Reconstruct the `Staking` call as is.
struct CallRec;
@@ -181,9 +168,11 @@ async fn calls_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
assert!(new_api.tx().staking().unbond(123_456_789_012_345).is_ok());
assert!(new_api.tx().staking().withdraw_unbonded(10).is_ok());
let api = metadata_to_api(metadata, &ctx).await;
// The calls should still be valid with this new type info:
assert!(api.tx().validate(&unbond_tx).is_ok());
assert!(api.tx().validate(&withdraw_unbonded_addr).is_ok());
// Change `Unbond` call but leave the rest as is.
struct CallRecSecond;
@@ -216,31 +205,24 @@ async fn calls_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
let api = metadata_to_api(metadata, &ctx).await;
// Unbond call should fail, while withdraw_unbonded remains compatible.
assert!(new_api.tx().staking().unbond(123_456_789_012_345).is_err());
assert!(new_api.tx().staking().withdraw_unbonded(10).is_ok());
assert!(api.tx().validate(&unbond_tx).is_err());
assert!(api.tx().validate(&withdraw_unbonded_addr).is_ok());
}
#[tokio::test]
async fn storage_check() {
let cxt = test_context().await;
let ctx = test_context().await;
let api = ctx.client();
let tx_count_addr = node_runtime::storage().system().extrinsic_count();
let tx_len_addr = node_runtime::storage().system().all_extrinsics_len();
// Ensure that `ExtrinsicCount` and `EventCount` storages are compatible before altering the metadata.
assert!(cxt
.api
.storage()
.system()
.extrinsic_count(None)
.await
.is_ok());
assert!(cxt
.api
.storage()
.system()
.all_extrinsics_len(None)
.await
.is_ok());
assert!(api.storage().validate(&tx_count_addr).is_ok());
assert!(api.storage().validate(&tx_len_addr).is_ok());
// Reconstruct the storage.
let storage = PalletStorageMetadata {
@@ -268,19 +250,11 @@ async fn storage_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
assert!(new_api
.storage()
.system()
.extrinsic_count(None)
.await
.is_ok());
assert!(new_api
.storage()
.system()
.all_extrinsics_len(None)
.await
.is_ok());
let api = metadata_to_api(metadata, &ctx).await;
// The addresses should still validate:
assert!(api.storage().validate(&tx_count_addr).is_ok());
assert!(api.storage().validate(&tx_len_addr).is_ok());
// Reconstruct the storage while modifying ExtrinsicCount.
let storage = PalletStorageMetadata {
@@ -309,17 +283,9 @@ async fn storage_check() {
..default_pallet()
};
let metadata = pallets_to_metadata(vec![pallet]);
let new_api = metadata_to_api(metadata, &cxt).await;
assert!(new_api
.storage()
.system()
.extrinsic_count(None)
.await
.is_err());
assert!(new_api
.storage()
.system()
.all_extrinsics_len(None)
.await
.is_ok());
let api = metadata_to_api(metadata, &ctx).await;
// The count route should fail now; the other will be ok still.
assert!(api.storage().validate(&tx_count_addr).is_err());
assert!(api.storage().validate(&tx_len_addr).is_ok());
}
+35 -44
View File
@@ -3,47 +3,48 @@
// see LICENSE for license details.
use crate::{
node_runtime::{
self,
DispatchError,
},
node_runtime,
pair_signer,
test_context,
utils::wait_for_blocks,
};
use sp_keyring::AccountKeyring;
#[tokio::test]
async fn storage_plain_lookup() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_plain_lookup() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Look up a plain value. Wait long enough that we don't get the genesis block data,
// because it may have no storage associated with it.
tokio::time::sleep(std::time::Duration::from_secs(6)).await;
let entry = ctx.api.storage().timestamp().now(None).await?;
wait_for_blocks(&api).await;
let addr = node_runtime::storage().timestamp().now();
let entry = api.storage().fetch_or_default(&addr, None).await?;
assert!(entry > 0);
Ok(())
}
#[tokio::test]
async fn storage_map_lookup() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_map_lookup() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let signer = pair_signer(AccountKeyring::Alice.pair());
let alice = AccountKeyring::Alice.to_account_id();
// Do some transaction to bump the Alice nonce to 1:
ctx.api
.tx()
.system()
.remark(vec![1, 2, 3, 4, 5])?
.sign_and_submit_then_watch_default(&signer)
let remark_tx = node_runtime::tx().system().remark(vec![1, 2, 3, 4, 5]);
api.tx()
.sign_and_submit_then_watch_default(&remark_tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
// Look up the nonce for the user (we expect it to be 1).
let entry = ctx.api.storage().system().account(&alice, None).await?;
let nonce_addr = node_runtime::storage().system().account(&alice);
let entry = api.storage().fetch_or_default(&nonce_addr, None).await?;
assert_eq!(entry.nonce, 1);
Ok(())
@@ -54,23 +55,15 @@ async fn storage_map_lookup() -> Result<(), subxt::Error<DispatchError>> {
// treated as a StorageKey (ie we should hash both values together with one hasher, rather
// than hash both values separately, or ignore the second value).
#[tokio::test]
async fn storage_n_mapish_key_is_properly_created(
) -> Result<(), subxt::Error<DispatchError>> {
async fn storage_n_mapish_key_is_properly_created() -> Result<(), subxt::Error> {
use codec::Encode;
use node_runtime::{
runtime_types::sp_core::crypto::KeyTypeId,
session::storage::KeyOwner,
};
use subxt::{
storage::StorageKeyPrefix,
StorageEntry,
};
use node_runtime::runtime_types::sp_core::crypto::KeyTypeId;
// This is what the generated code hashes a `session().key_owner(..)` key into:
let actual_key_bytes = KeyOwner(&KeyTypeId([1, 2, 3, 4]), &[5u8, 6, 7, 8])
.key()
.final_key(StorageKeyPrefix::new::<KeyOwner>())
.0;
let actual_key_bytes = node_runtime::storage()
.session()
.key_owner(KeyTypeId([1, 2, 3, 4]), [5u8, 6, 7, 8])
.to_bytes();
// Let's manually hash to what we assume it should be and compare:
let expected_key_bytes = {
@@ -89,8 +82,9 @@ async fn storage_n_mapish_key_is_properly_created(
}
#[tokio::test]
async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error<DispatchError>> {
async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
// Boilerplate; we create a new asset class with ID 99, and then
// we "approveTransfer" of some of this asset class. This gives us an
@@ -98,30 +92,27 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error<DispatchError
let signer = pair_signer(AccountKeyring::Alice.pair());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
ctx.api
.tx()
let tx1 = node_runtime::tx()
.assets()
.create(99, alice.clone().into(), 1)?
.sign_and_submit_then_watch_default(&signer)
.create(99, alice.clone().into(), 1);
let tx2 = node_runtime::tx()
.assets()
.approve_transfer(99, bob.clone().into(), 123);
api.tx()
.sign_and_submit_then_watch_default(&tx1, &signer)
.await?
.wait_for_finalized_success()
.await?;
ctx.api
.tx()
.assets()
.approve_transfer(99, bob.clone().into(), 123)?
.sign_and_submit_then_watch_default(&signer)
api.tx()
.sign_and_submit_then_watch_default(&tx2, &signer)
.await?
.wait_for_finalized_success()
.await?;
// The actual test; look up this approval in storage:
let entry = ctx
.api
.storage()
.assets()
.approvals(&99, &alice, &bob, None)
.await?;
let addr = node_runtime::storage().assets().approvals(99, &alice, &bob);
let entry = api.storage().fetch(&addr, None).await?;
assert_eq!(entry.map(|a| a.amount), Some(123));
Ok(())
}
+8 -30
View File
@@ -10,20 +10,14 @@ pub(crate) use crate::{
use sp_core::sr25519::Pair;
use sp_keyring::AccountKeyring;
use subxt::{
Client,
DefaultConfig,
PairSigner,
SubstrateExtrinsicParams,
tx::PairSigner,
SubstrateConfig,
};
/// substrate node should be installed on the $PATH
const SUBSTRATE_NODE_PATH: &str = "substrate";
pub type NodeRuntimeParams = SubstrateExtrinsicParams<DefaultConfig>;
pub async fn test_node_process_with(
key: AccountKeyring,
) -> TestNodeProcess<DefaultConfig> {
pub async fn test_context_with(key: AccountKeyring) -> TestContext {
let path = std::env::var("SUBSTRATE_NODE_PATH").unwrap_or_else(|_| {
if which::which(SUBSTRATE_NODE_PATH).is_err() {
panic!("A substrate binary should be installed on your path for integration tests. \
@@ -32,35 +26,19 @@ pub async fn test_node_process_with(
SUBSTRATE_NODE_PATH.to_string()
});
let proc = TestNodeProcess::<DefaultConfig>::build(path.as_str())
let proc = TestContext::build(path.as_str())
.with_authority(key)
.spawn::<DefaultConfig>()
.spawn::<SubstrateConfig>()
.await;
proc.unwrap()
}
pub async fn test_node_process() -> TestNodeProcess<DefaultConfig> {
test_node_process_with(AccountKeyring::Alice).await
}
pub struct TestContext {
pub node_proc: TestNodeProcess<DefaultConfig>,
pub api: node_runtime::RuntimeApi<DefaultConfig, NodeRuntimeParams>,
}
impl TestContext {
pub fn client(&self) -> &Client<DefaultConfig> {
&self.api.client
}
}
pub type TestContext = TestNodeProcess<SubstrateConfig>;
pub async fn test_context() -> TestContext {
tracing_subscriber::fmt::try_init().ok();
let node_proc = test_node_process_with(AccountKeyring::Alice).await;
let api = node_proc.client().clone().to_runtime_api();
TestContext { node_proc, api }
test_context_with(AccountKeyring::Alice).await
}
pub fn pair_signer(pair: Pair) -> PairSigner<DefaultConfig, Pair> {
pub fn pair_signer(pair: Pair) -> PairSigner<SubstrateConfig, Pair> {
PairSigner::new(pair)
}
@@ -4,6 +4,8 @@
mod context;
mod node_proc;
mod wait_for_blocks;
pub use context::*;
pub use node_proc::TestNodeProcess;
pub use wait_for_blocks::wait_for_blocks;
@@ -16,16 +16,14 @@ use std::{
process,
};
use subxt::{
Client,
ClientBuilder,
Config,
OnlineClient,
};
/// Spawn a local substrate node for testing subxt.
pub struct TestNodeProcess<R: Config> {
proc: process::Child,
client: Client<R>,
ws_url: String,
client: OnlineClient<R>,
}
impl<R> Drop for TestNodeProcess<R>
@@ -61,13 +59,8 @@ where
}
/// Returns the subxt client connected to the running node.
pub fn client(&self) -> &Client<R> {
&self.client
}
/// Returns the address to which the client is connected.
pub fn ws_url(&self) -> &str {
&self.ws_url
pub fn client(&self) -> OnlineClient<R> {
self.client.clone()
}
}
@@ -129,15 +122,9 @@ impl TestNodeProcessBuilder {
let ws_url = format!("ws://127.0.0.1:{}", ws_port);
// Connect to the node with a subxt client:
let client = ClientBuilder::new().set_url(ws_url.clone()).build().await;
let client = OnlineClient::from_url(ws_url.clone()).await;
match client {
Ok(client) => {
Ok(TestNodeProcess {
proc,
client,
ws_url,
})
}
Ok(client) => Ok(TestNodeProcess { proc, client }),
Err(err) => {
let err = format!("Failed to connect to node rpc at {}: {}", ws_url, err);
tracing::error!("{}", err);
@@ -0,0 +1,17 @@
// 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 subxt::{
client::OnlineClientT,
Config,
};
/// Wait for blocks to be produced before running tests. Waiting for two blocks
/// (the genesis block and another one) seems to be enough to allow tests
/// like `dry_run_passes` to work properly.
pub async fn wait_for_blocks<C: Config>(api: &impl OnlineClientT<C>) {
let mut sub = api.rpc().subscribe_blocks().await.unwrap();
sub.next().await;
sub.next().await;
}