v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)

* WIP integrating new frame-decode and working out new storage APIS

* WIP: first pass adding new storage things to subxt-core

* Second pass over Address type and start impl in Subxt

* WIP new storage APIs

* WIP New storage APIs roughly completed, lots of errors still

* Remove PlainorMap enum; plain and map values now use same struct to simplify usage

* Begin 'fixing' errors

* WIP splitting errors and tidying payload/address traits

* Get subxt-core compiling

* Small fixes in subxt-core and remove metadata mod

* subxt-core: cargo check --all-targets passes

* Fix test

* WIP starting to update subxt from subxt-core changes

* WIP splitting up subxt errors into smaller variants

* WIP errors: add DispatchError errors

* Port new Storage APIs to subxt-core

* cargo check -p subxt passes

* Quick-fix errors in subxt-cli (explore subcommand)

* fmt

* Finish fixing codegen up and start fixing examples

* get Subxt examples compiling and bytes_at for constants

* Add some arcs to limit lifetimes in subxt/subxt-core storage APIs

* A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt

* Update codegen test

* cargo check --all-targets passing

* cargo check --features 'unstable-light-client' passing

* clippy

* Remove unused dep in subxt

* use published frame-decode

* fix wasm-example

* Add new tx extension to fix daily tests

* Remove unused subxt_core::dynamic::DecodedValue type

* Update book to match changes

* Update docs to fix more broken bits

* Add missing docs

* fmt

* allow larger result errs for now

* Add missing alloc imports in subxt-core

* Fix doc tests and fix bug getting constant info

* Fix V14 -> Metadata transform for storage & constants

* Fix parachain example

* Fix FFI example

* BlockLength decodes t ostruct, not u128

* use fetch/iter shorthands rather than entry in most storage tests

* Fix some integration tests

* Fix Runtime codegen tests

* Expose the dynamic custom_value selecter and use in a UI test

* Update codegen metadata

* Tidy CLI storage query and support (str,str) as a storage address

* Add (str,str) as valid constant address too

* Show string tuple in constants example

* Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them

* clippy
This commit is contained in:
James Wilson
2025-11-10 11:38:07 +00:00
committed by GitHub
parent 7b4b23981c
commit 8329990a33
138 changed files with 11154 additions and 16363 deletions
Generated
+6 -6
View File
@@ -1953,9 +1953,9 @@ dependencies = [
[[package]]
name = "frame-decode"
version = "0.10.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "641e3739fa708a278d35b008a05244008c221240abc3e1c27138466c13e999ed"
checksum = "f8c26b7a0fb90c471cc2c90b1a9fa60ab78c6a35a4079130feefd625999708d7"
dependencies = [
"frame-metadata 23.0.0",
"parity-scale-codec",
@@ -4464,9 +4464,9 @@ dependencies = [
[[package]]
name = "scale-info-legacy"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5da3f59983b08a37d8d979d2326bdc00e8cca57b3d28fb05bdc0f6d7c28600c"
checksum = "bd183213b6831b6bc08fda67a310bf9299889d669e264a2a2168679079a0c522"
dependencies = [
"hashbrown 0.15.3",
"scale-type-resolver",
@@ -4520,9 +4520,9 @@ dependencies = [
[[package]]
name = "scale-value"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
dependencies = [
"base58",
"blake2",
+3 -3
View File
@@ -81,7 +81,7 @@ darling = "0.20.10"
derive-where = "1.2.7"
either = { version = "1.13.0", default-features = false }
finito = { version = "0.1.0", default-features = false }
frame-decode = { version = "0.10.0", default-features = false }
frame-decode = { version = "0.11.1", default-features = false }
frame-metadata = { version = "23.0.0", default-features = false }
futures = { version = "0.3.31", default-features = false, features = ["std"] }
getrandom = { version = "0.2", default-features = false }
@@ -98,12 +98,12 @@ proc-macro2 = "1.0.86"
quote = "1.0.37"
regex = { version = "1.11.0", default-features = false }
scale-info = { version = "2.11.4", default-features = false }
scale-value = { version = "0.18.0", default-features = false }
scale-value = { version = "0.18.1", default-features = false }
scale-bits = { version = "0.7.0", default-features = false }
scale-decode = { version = "0.16.0", default-features = false }
scale-encode = { version = "0.10.0", default-features = false }
scale-type-resolver = { version = "0.2.0" }
scale-info-legacy = { version = "0.2.3" }
scale-info-legacy = { version = "0.2.4" }
scale-typegen = "0.11.1"
scale-typegen-description = "0.11.0"
serde = { version = "1.0.210", default-features = false, features = ["derive"] }
+14 -27
View File
@@ -215,7 +215,6 @@ struct StorageEntryDiff {
key_different: bool,
value_different: bool,
default_different: bool,
modifier_different: bool,
}
impl StorageEntryDiff {
@@ -225,41 +224,32 @@ impl StorageEntryDiff {
metadata_1: &Metadata,
metadata_2: &Metadata,
) -> Self {
let value_1_ty_id = storage_entry_1.entry_type().value_ty();
let value_1_ty_id = storage_entry_1.value_ty();
let value_1_hash = metadata_1
.type_hash(value_1_ty_id)
.expect("type is in metadata; qed");
let value_2_ty_id = storage_entry_2.entry_type().value_ty();
let value_2_ty_id = storage_entry_2.value_ty();
let value_2_hash = metadata_2
.type_hash(value_2_ty_id)
.expect("type is in metadata; qed");
let value_different = value_1_hash != value_2_hash;
let key_1_hash = storage_entry_1
.entry_type()
.key_ty()
.map(|key_ty| {
metadata_1
.type_hash(key_ty)
.expect("type is in metadata; qed")
})
.unwrap_or_default();
let key_2_hash = storage_entry_2
.entry_type()
.key_ty()
.map(|key_ty| {
metadata_2
.type_hash(key_ty)
.expect("type is in metadata; qed")
})
.unwrap_or_default();
let key_different = key_1_hash != key_2_hash;
let key_parts_same = storage_entry_1.keys().len() == storage_entry_2.keys().len()
&& storage_entry_1
.keys()
.zip(storage_entry_2.keys())
.all(|(a, b)| {
let a_hash = metadata_1.type_hash(a.key_id).expect("type is in metadata");
let b_hash = metadata_2.type_hash(b.key_id).expect("type is in metadata");
a.hasher == b.hasher && a_hash == b_hash
});
let key_different = !key_parts_same;
StorageEntryDiff {
key_different,
value_different,
default_different: storage_entry_1.default_bytes() != storage_entry_2.default_bytes(),
modifier_different: storage_entry_1.modifier() != storage_entry_2.modifier(),
default_different: storage_entry_1.default_value() != storage_entry_2.default_value(),
}
}
@@ -271,9 +261,6 @@ impl StorageEntryDiff {
if self.value_different {
strings.push("value type");
}
if self.modifier_different {
strings.push("modifier");
}
if self.default_different {
strings.push("default value");
}
+1 -1
View File
@@ -12,7 +12,7 @@ use subxt::utils::H256;
use subxt::{
OfflineClient,
config::SubstrateConfig,
metadata::{Metadata, types::PalletMetadata},
metadata::{Metadata, PalletMetadata},
};
use crate::utils::{
@@ -2,7 +2,7 @@ use clap::Args;
use color_eyre::eyre::eyre;
use indoc::{formatdoc, writedoc};
use scale_typegen_description::type_description;
use subxt::metadata::{Metadata, types::PalletMetadata};
use subxt::metadata::{Metadata, PalletMetadata};
use crate::utils::{Indent, SyntaxHighlight, first_paragraph_of_docs, format_scale_value};
+1 -1
View File
@@ -2,7 +2,7 @@ use clap::Args;
use color_eyre::eyre::eyre;
use indoc::{formatdoc, writedoc};
use scale_info::{Variant, form::PortableForm};
use subxt::metadata::{Metadata, types::PalletMetadata};
use subxt::metadata::{Metadata, PalletMetadata};
use crate::utils::{Indent, fields_description, first_paragraph_of_docs};
+34 -30
View File
@@ -1,17 +1,11 @@
use clap::Args;
use color_eyre::{
eyre::{bail, eyre},
owo_colors::OwoColorize,
};
use color_eyre::{eyre::bail, owo_colors::OwoColorize};
use indoc::{formatdoc, writedoc};
use scale_typegen_description::type_description;
use scale_value::Value;
use std::fmt::Write;
use std::write;
use subxt::metadata::{
Metadata,
types::{PalletMetadata, StorageEntryType, StorageMetadata},
};
use subxt::metadata::{Metadata, PalletMetadata, StorageMetadata};
use crate::utils::{
FileOrUrl, Indent, SyntaxHighlight, create_client, first_paragraph_of_docs,
@@ -75,12 +69,7 @@ pub async fn explore_storage(
);
};
let (return_ty_id, key_ty_id) = match storage.entry_type() {
StorageEntryType::Plain(value) => (*value, None),
StorageEntryType::Map {
value_ty, key_ty, ..
} => (*value_ty, Some(*key_ty)),
};
let return_ty_id = storage.value_ty();
let key_value_placeholder = "<KEY_VALUE>".blue();
@@ -114,15 +103,30 @@ pub async fn explore_storage(
"}?;
// inform user about shape of the key if it can be provided:
if let Some(key_ty_id) = key_ty_id {
let key_ty_description = type_description(key_ty_id, metadata.types(), true)
.expect("No type Description")
.indent(4)
.highlight();
let storage_keys = storage.keys().collect::<Vec<_>>();
if !storage_keys.is_empty() {
let key_ty_description = format!(
"({})",
storage_keys
.iter()
.map(|key| type_description(key.key_id, metadata.types(), true)
.expect("No type Description"))
.collect::<Vec<_>>()
.join(", ")
)
.indent(4)
.highlight();
let key_ty_example = type_example(key_ty_id, metadata.types())
.indent(4)
.highlight();
let key_ty_example = format!(
"({})",
storage_keys
.iter()
.map(|key| type_example(key.key_id, metadata.types()).to_string())
.collect::<Vec<_>>()
.join(", ")
)
.indent(4)
.highlight();
writedoc! {output, "
@@ -144,7 +148,8 @@ pub async fn explore_storage(
return Ok(());
}
let storage_entry_keys: Vec<Value> = match (!trailing_args.is_empty(), key_ty_id.is_some()) {
let storage_entry_keys: Vec<Value> = match (!trailing_args.is_empty(), !storage_keys.is_empty())
{
// keys provided, keys not needed.
(true, false) => {
let trailing_args_str = trailing_args.join(" ");
@@ -190,18 +195,17 @@ pub async fn explore_storage(
// construct the client:
let client = create_client(&file_or_url).await?;
let storage_query = subxt::dynamic::storage(pallet_name, storage.name(), storage_entry_keys);
let decoded_value_thunk_or_none = client
// Fetch the value:
let storage_value = client
.storage()
.at_latest()
.await?
.fetch(&storage_query)
.await?;
.fetch((pallet_name, storage.name()), storage_entry_keys)
.await?
.decode()?;
let decoded_value_thunk =
decoded_value_thunk_or_none.ok_or(eyre!("Value not found in storage."))?;
let value = storage_value.to_string().highlight();
let value = decoded_value_thunk.to_value()?.to_string().highlight();
writedoc! {output, "
The value of the storage entry is:
+8 -9
View File
@@ -101,15 +101,13 @@ pub async fn run<'a>(
return format!("The method does not require an {input_value_placeholder}");
}
let fields: Vec<(Option<&str>, u32)> = method
.inputs()
.map(|f| (Some(f.name.as_str()), f.ty))
.collect();
let fields: Vec<(Option<&str>, u32)> =
method.inputs().map(|f| (Some(&*f.name), f.id)).collect();
let fields_description =
fields_description(&fields, method.name(), metadata.types()).indent(4);
let fields_example =
fields_composite_example(method.inputs().map(|e| e.ty), metadata.types())
fields_composite_example(method.inputs().map(|e| e.id), metadata.types())
.indent(4)
.highlight();
@@ -164,13 +162,14 @@ pub async fn run<'a>(
{value_str}
"}?;
// encode, then decode. This ensures that the scale value is of the correct shape for the param:
let bytes = value.encode_as_type(ty.ty, metadata.types())?;
let value = Value::decode_as_type(&mut &bytes[..], ty.ty, metadata.types())?;
let bytes = value.encode_as_type(ty.id, metadata.types())?;
let value = Value::decode_as_type(&mut &bytes[..], ty.id, metadata.types())?;
Ok(value)
})
.collect::<color_eyre::Result<Vec<Value>>>()?;
let method_call = subxt::dynamic::runtime_api_call(api_name, method.name(), args_data);
let method_call =
subxt::dynamic::runtime_api_call::<_, Value>(api_name, method.name(), args_data);
let client = create_client(&file_or_url).await?;
let output_value = client
.runtime_api()
@@ -179,7 +178,7 @@ pub async fn run<'a>(
.call(method_call)
.await?;
let output_value = output_value.to_value()?.to_string().highlight();
let output_value = output_value.to_string().highlight();
writedoc! {output, "
Returned value:
+1 -1
View File
@@ -1,3 +1,3 @@
# result_large_err lint complains if error variant is 128 bytes or more by default.
# Our error is. Let's up this limit a bit for now to avoid lots of warnings.
large-error-threshold = 256
large-error-threshold = 512
+3 -2
View File
@@ -57,16 +57,17 @@ fn generate_custom_value_fn(
.types()
.resolve(custom_value.type_id())
.is_some();
let (return_ty, decodable) = if type_is_valid {
let return_ty = type_gen
.resolve_type_path(custom_value.type_id())
.expect("type is in metadata; qed")
.to_token_stream(type_gen.settings());
let decodable = quote!(#crate_path::utils::Yes);
let decodable = quote!(#crate_path::utils::Maybe);
(return_ty, decodable)
} else {
// if type registry does not contain the type, we can just return the Encoded scale bytes.
(quote!(()), quote!(()))
(quote!(()), quote!(#crate_path::utils::No))
};
Some(quote!(
+49 -59
View File
@@ -12,7 +12,45 @@ use scale_typegen::typegen::ir::ToTokensWithSettings;
use std::collections::HashSet;
use subxt_metadata::{PalletMetadata, ViewFunctionMetadata};
pub fn generate_pallet_view_functions(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
if !pallet.has_view_functions() {
// If there are no view functions in this pallet, we
// don't generate anything.
return Ok(quote! {});
}
let view_functions: Vec<_> = pallet
.view_functions()
.map(|vf| generate_pallet_view_function(pallet.name(), vf, type_gen, crate_path))
.collect::<Result<_, _>>()?;
let view_functions_types = view_functions.iter().map(|(apis, _)| apis);
let view_functions_methods = view_functions.iter().map(|(_, getters)| getters);
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
pub mod view_functions {
use super::root_mod;
use super::#types_mod_ident;
pub struct ViewFunctionsApi;
impl ViewFunctionsApi {
#( #view_functions_methods )*
}
#( #view_functions_types )*
}
})
}
fn generate_pallet_view_function(
pallet_name: &str,
view_function: ViewFunctionMetadata<'_>,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
@@ -20,9 +58,7 @@ fn generate_pallet_view_function(
let types_mod_ident = type_gen.types_mod_ident();
let view_function_name_str = view_function.name();
let view_function_name_ident = format_ident!("{}", view_function_name_str);
let query_id = view_function.query_id();
let view_function_name_ident = format_ident!("{view_function_name_str}");
let validation_hash = view_function.hash();
let docs = view_function.docs();
@@ -68,7 +104,7 @@ fn generate_pallet_view_function(
// Path to the actual type we'll have generated for this input.
let type_path = type_gen
.resolve_type_path(input.ty)
.resolve_type_path(input.id)
.expect("view function input type is in metadata; qed")
.to_token_stream(type_gen.settings());
@@ -81,12 +117,11 @@ fn generate_pallet_view_function(
.collect()
};
let input_struct_params = view_function_inputs
let input_tuple_types = view_function_inputs
.iter()
.map(|i| {
let arg = &i.name;
let ty = &i.type_alias;
quote!(pub #arg: #ty)
quote!(#view_function_name_ident::#ty)
})
.collect::<Vec<_>>();
@@ -111,19 +146,12 @@ fn generate_pallet_view_function(
.resolve_type_path(view_function.output_ty())?
.to_token_stream(type_gen.settings());
let input_struct_derives = type_gen.settings().derives.default_derives();
// Define the input and output type bits.
let view_function_def = quote!(
let view_function_types = quote!(
pub mod #view_function_name_ident {
use super::root_mod;
use super::#types_mod_ident;
#input_struct_derives
pub struct Input {
#(#input_struct_params,)*
}
#(#input_type_aliases)*
pub mod output {
@@ -134,61 +162,23 @@ fn generate_pallet_view_function(
);
// Define the getter method that will live on the `ViewFunctionApi` type.
let view_function_getter = quote!(
let view_function_method = quote!(
#docs
pub fn #view_function_name_ident(
&self,
#(#input_args),*
) -> #crate_path::view_functions::payload::StaticPayload<
#view_function_name_ident::Input,
(#(#input_tuple_types,)*),
#view_function_name_ident::output::Output
> {
#crate_path::view_functions::payload::StaticPayload::new_static(
[#(#query_id,)*],
#view_function_name_ident::Input {
#(#input_param_names,)*
},
#pallet_name,
#view_function_name_str,
(#(#input_param_names,)*),
[#(#validation_hash,)*],
)
}
);
Ok((view_function_def, view_function_getter))
}
pub fn generate_pallet_view_functions(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
if !pallet.has_view_functions() {
// If there are no view functions in this pallet, we
// don't generate anything.
return Ok(quote! {});
}
let view_functions: Vec<_> = pallet
.view_functions()
.map(|vf| generate_pallet_view_function(vf, type_gen, crate_path))
.collect::<Result<_, _>>()?;
let view_functions_defs = view_functions.iter().map(|(apis, _)| apis);
let view_functions_getters = view_functions.iter().map(|(_, getters)| getters);
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
pub mod view_functions {
use super::root_mod;
use super::#types_mod_ident;
pub struct ViewFunctionsApi;
impl ViewFunctionsApi {
#( #view_functions_getters )*
}
#( #view_functions_defs )*
}
})
Ok((view_function_types, view_function_method))
}
+190 -193
View File
@@ -16,170 +16,6 @@ use quote::{format_ident, quote};
use crate::CodegenError;
/// Generates runtime functions for the given API metadata.
fn generate_runtime_api(
api: RuntimeApiMetadata,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
// Trait name must remain as is (upper case) to identify the runtime call.
let trait_name_str = api.name();
// The snake case for the trait name.
let trait_name_snake = format_ident!("{}", api.name().to_snake_case());
let docs = api.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let structs_and_methods = api
.methods()
.map(|method| {
let method_name = format_ident!("{}", method.name());
let method_name_str = method.name();
let docs = method.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let mut unique_names = HashSet::new();
let mut unique_aliases = HashSet::new();
let inputs: Vec<_> = method
.inputs()
.enumerate()
.map(|(idx, input)| {
// These are method names, which can just be '_', but struct field names can't
// just be an underscore, so fix any such names we find to work in structs.
let mut name = input.name.trim_start_matches('_').to_string();
if name.is_empty() {
name = format!("_{idx}");
}
while !unique_names.insert(name.clone()) {
// Name is already used, append the index until it is unique.
name = format!("{name}_param{idx}");
}
let mut alias = name.to_upper_camel_case();
// Note: name is not empty.
if alias.as_bytes()[0].is_ascii_digit() {
alias = format!("Param{alias}");
}
while !unique_aliases.insert(alias.clone()) {
alias = format!("{alias}Param{idx}");
}
let (alias_name, name) = (format_ident!("{alias}"), format_ident!("{name}"));
// Generate alias for runtime type.
let ty = type_gen
.resolve_type_path(input.ty)
.expect("runtime api input type is in metadata; qed")
.to_token_stream(type_gen.settings());
let aliased_param = quote!( pub type #alias_name = #ty; );
// Structures are placed on the same level as the alias module.
let struct_ty_path = quote!( #method_name::#alias_name );
let struct_param = quote!(#name: #struct_ty_path);
// Function parameters must be indented by `types`.
let fn_param = quote!(#name: types::#struct_ty_path);
(fn_param, struct_param, name, aliased_param)
})
.collect();
let fn_params = inputs.iter().map(|(fn_param, _, _, _)| fn_param);
let struct_params = inputs.iter().map(|(_, struct_param, _, _)| struct_param);
let param_names = inputs.iter().map(|(_, _, name, _)| name);
let type_aliases = inputs.iter().map(|(_, _, _, aliased_param)| aliased_param);
let types_mod_ident = type_gen.types_mod_ident();
let output = type_gen.resolve_type_path(method.output_ty())?.to_token_stream(type_gen.settings());
let aliased_module = quote!(
pub mod #method_name {
use super::#types_mod_ident;
#( #type_aliases )*
// Guard the `Output` name against collisions by placing it in a dedicated module.
pub mod output {
use super::#types_mod_ident;
pub type Output = #output;
}
}
);
// From the method metadata generate a structure that holds
// all parameter types. This structure is used with metadata
// to encode parameters to the call via `encode_as_fields_to`.
let derives = type_gen.settings().derives.default_derives();
let struct_name = format_ident!("{}", method.name().to_upper_camel_case());
let struct_input = quote!(
#aliased_module
#derives
pub struct #struct_name {
#( pub #struct_params, )*
}
);
let call_hash = method.hash();
let method = quote!(
#docs
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::payload::StaticPayload<types::#struct_name, types::#method_name::output::Output> {
#crate_path::runtime_api::payload::StaticPayload::new_static(
#trait_name_str,
#method_name_str,
types::#struct_name { #( #param_names, )* },
[#(#call_hash,)*],
)
}
);
Ok((struct_input, method))
})
.collect::<Result<Vec<_>, CodegenError>>()?;
let trait_name = format_ident!("{}", trait_name_str);
let structs = structs_and_methods.iter().map(|(struct_, _)| struct_);
let methods = structs_and_methods.iter().map(|(_, method)| method);
let types_mod_ident = type_gen.types_mod_ident();
let runtime_api = quote!(
pub mod #trait_name_snake {
use super::root_mod;
use super::#types_mod_ident;
#docs
pub struct #trait_name;
impl #trait_name {
#( #methods )*
}
pub mod types {
use super::#types_mod_ident;
#( #structs )*
}
}
);
// A getter for the `RuntimeApi` to get the trait structure.
let trait_getter = quote!(
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
#trait_name_snake::#trait_name
}
);
Ok((runtime_api, trait_getter))
}
/// Generate the runtime APIs.
pub fn generate_runtime_apis(
metadata: &Metadata,
@@ -192,8 +28,8 @@ pub fn generate_runtime_apis(
.map(|api| generate_runtime_api(api, type_gen, crate_path))
.collect::<Result<_, _>>()?;
let runtime_apis_def = runtime_fns.iter().map(|(apis, _)| apis);
let runtime_apis_getters = runtime_fns.iter().map(|(_, getters)| getters);
let trait_defs = runtime_fns.iter().map(|(apis, _)| apis);
let trait_getters = runtime_fns.iter().map(|(_, getters)| getters);
Ok(quote! {
pub mod runtime_apis {
@@ -205,14 +41,196 @@ pub fn generate_runtime_apis(
pub struct RuntimeApi;
impl RuntimeApi {
#( #runtime_apis_getters )*
#( #trait_getters )*
}
#( #runtime_apis_def )*
#( #trait_defs )*
}
})
}
/// Generates runtime functions for the given API metadata.
fn generate_runtime_api(
api: RuntimeApiMetadata,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
let types_mod_ident = type_gen.types_mod_ident();
// Trait name must remain as is (upper case) to identify the runtime call.
let trait_name_str = api.name();
// The snake case for the trait name.
let trait_name_snake = format_ident!("{}", api.name().to_snake_case());
let docs = api.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let types_and_methods = api
.methods()
.map(|method| {
let method_name = format_ident!("{}", method.name());
let method_name_str = method.name();
let validation_hash = method.hash();
let docs = method.docs();
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
struct Input {
name: syn::Ident,
type_alias: syn::Ident,
type_path: TokenStream2,
}
let runtime_api_inputs: Vec<Input> = {
let mut unique_names = HashSet::new();
let mut unique_aliases = HashSet::new();
method
.inputs()
.enumerate()
.map(|(idx, input)| {
// The method argument name is either the input name or the
// index (eg _1, _2 etc) if one isn't provided.
// if we get unlucky we'll end up with param_param1 etc.
let mut name = input.name.trim_start_matches('_').to_string();
if name.is_empty() {
name = format!("_{idx}");
}
while !unique_names.insert(name.clone()) {
name = format!("{name}_param{idx}");
}
// The alias is either InputName if provided, or Param1, Param2 etc if not.
// If we get unlucky we may even end up with ParamParam1 etc.
let mut alias = name.trim_start_matches('_').to_upper_camel_case();
// Note: name is not empty.
if alias.as_bytes()[0].is_ascii_digit() {
alias = format!("Param{alias}");
}
while !unique_aliases.insert(alias.clone()) {
alias = format!("{alias}Param{idx}");
}
// Generate alias for runtime type.
let type_path = type_gen
.resolve_type_path(input.id)
.expect("runtime api input type is in metadata; qed")
.to_token_stream(type_gen.settings());
Input {
name: format_ident!("{name}"),
type_alias: format_ident!("{alias}"),
type_path,
}
})
.collect()
};
let input_tuple_types = runtime_api_inputs
.iter()
.map(|i| {
let ty = &i.type_alias;
quote!(#method_name::#ty)
})
.collect::<Vec<_>>();
let input_args = runtime_api_inputs
.iter()
.map(|i| {
let arg = &i.name;
let ty = &i.type_alias;
quote!(#arg: #method_name::#ty)
})
.collect::<Vec<_>>();
let input_param_names = runtime_api_inputs.iter().map(|i| &i.name);
let input_type_aliases = runtime_api_inputs.iter().map(|i| {
let ty = &i.type_alias;
let path = &i.type_path;
quote!(pub type #ty = #path;)
});
let output_type_path = type_gen
.resolve_type_path(method.output_ty())?
.to_token_stream(type_gen.settings());
// Define the input and output type bits for the method.
let runtime_api_types = quote! {
pub mod #method_name {
use super::root_mod;
use super::#types_mod_ident;
#(#input_type_aliases)*
pub mod output {
use super::#types_mod_ident;
pub type Output = #output_type_path;
}
}
};
// Define the getter method that will live on the `ViewFunctionApi` type.
let runtime_api_method = quote!(
#docs
pub fn #method_name(
&self,
#(#input_args),*
) -> #crate_path::runtime_api::payload::StaticPayload<
(#(#input_tuple_types,)*),
#method_name::output::Output
> {
#crate_path::runtime_api::payload::StaticPayload::new_static(
#trait_name_str,
#method_name_str,
(#(#input_param_names,)*),
[#(#validation_hash,)*],
)
}
);
Ok((runtime_api_types, runtime_api_method))
})
.collect::<Result<Vec<_>, CodegenError>>()?;
let trait_name = format_ident!("{}", trait_name_str);
let types = types_and_methods.iter().map(|(types, _)| types);
let methods = types_and_methods.iter().map(|(_, methods)| methods);
// The runtime API definition and types.
let trait_defs = quote!(
pub mod #trait_name_snake {
use super::root_mod;
use super::#types_mod_ident;
#docs
pub struct #trait_name;
impl #trait_name {
#( #methods )*
}
#( #types )*
}
);
// A getter for the `RuntimeApi` to get the trait structure.
let trait_getter = quote!(
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
#trait_name_snake::#trait_name
}
);
Ok((trait_defs, trait_getter))
}
#[cfg(test)]
mod tests {
use crate::RuntimeGenerator;
@@ -295,14 +313,9 @@ mod tests {
let code = generate_code(runtime_apis);
let structure = quote! {
pub struct Test {
pub foo: test::Foo,
pub bar: test::Bar,
}
};
let expected_alias = quote!(
pub mod test {
use super::root_mod;
use super::runtime_types;
pub type Foo = ::core::primitive::bool;
pub type Bar = ::core::primitive::bool;
@@ -312,7 +325,7 @@ mod tests {
}
}
);
assert!(code.contains(&structure.to_string()));
assert!(code.contains(&expected_alias.to_string()));
}
@@ -345,15 +358,9 @@ mod tests {
let code = generate_code(runtime_apis);
let structure = quote! {
pub struct Test {
pub a: test::A,
pub a_param1: test::AParam1,
pub a_param2: test::AParam2,
}
};
let expected_alias = quote!(
pub mod test {
use super::root_mod;
use super::runtime_types;
pub type A = ::core::primitive::bool;
pub type AParam1 = ::core::primitive::bool;
@@ -365,7 +372,6 @@ mod tests {
}
);
assert!(code.contains(&structure.to_string()));
assert!(code.contains(&expected_alias.to_string()));
}
@@ -406,17 +412,9 @@ mod tests {
let code = generate_code(runtime_apis);
let structure = quote! {
pub struct Test {
pub _0: test::Param0,
pub a: test::A,
pub param_0: test::Param0Param2,
pub _3: test::Param3,
pub param_0_param_2: test::Param0Param2Param4,
}
};
let expected_alias = quote!(
pub mod test {
use super::root_mod;
use super::runtime_types;
pub type Param0 = ::core::primitive::bool;
pub type A = ::core::primitive::bool;
@@ -430,7 +428,6 @@ mod tests {
}
);
assert!(code.contains(&structure.to_string()));
assert!(code.contains(&expected_alias.to_string()));
}
}
+97 -230
View File
@@ -2,14 +2,11 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use heck::{ToSnakeCase as _, ToUpperCamelCase};
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
use heck::ToSnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_info::TypeDef;
use scale_typegen::TypeGenerator;
use subxt_metadata::{
PalletMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher,
};
use subxt_metadata::{PalletMetadata, StorageEntryMetadata};
use super::CodegenError;
@@ -29,33 +26,34 @@ pub fn generate_storage(
crate_path: &syn::Path,
) -> Result<TokenStream2, CodegenError> {
let Some(storage) = pallet.storage() else {
// If there are no storage entries in this pallet, we
// don't generate anything.
return Ok(quote!());
};
let (storage_fns, alias_modules): (Vec<TokenStream2>, Vec<TokenStream2>) = storage
let storage_entries = storage
.entries()
.iter()
.map(|entry| generate_storage_entry_fns(type_gen, pallet, entry, crate_path))
.collect::<Result<Vec<_>, CodegenError>>()?
.into_iter()
.unzip();
.collect::<Result<Vec<_>, CodegenError>>()?;
let storage_entry_types = storage_entries.iter().map(|(types, _)| types);
let storage_entry_methods = storage_entries.iter().map(|(_, method)| method);
let types_mod_ident = type_gen.types_mod_ident();
Ok(quote! {
pub mod storage {
use super::root_mod;
use super::#types_mod_ident;
pub mod types {
use super::#types_mod_ident;
#( #alias_modules )*
}
pub struct StorageApi;
impl StorageApi {
#( #storage_fns )*
#( #storage_entry_methods )*
}
#( #storage_entry_types )*
}
})
}
@@ -67,239 +65,108 @@ fn generate_storage_entry_fns(
storage_entry: &StorageEntryMetadata,
crate_path: &syn::Path,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
let snake_case_name = storage_entry.name().to_snake_case();
let storage_entry_ty = storage_entry.entry_type().value_ty();
let storage_entry_value_ty = type_gen
.resolve_type_path(storage_entry_ty)
.expect("storage type is in metadata; qed")
.to_token_stream(type_gen.settings());
let alias_name = format_ident!("{}", storage_entry.name().to_upper_camel_case());
let alias_module_name = format_ident!("{snake_case_name}");
let alias_storage_path = quote!( types::#alias_module_name::#alias_name );
struct MapEntryKey {
arg_name: Ident,
alias_type_def: TokenStream,
alias_type_path: TokenStream,
hasher: StorageHasher,
}
let map_entry_key = |idx, id, hasher| -> MapEntryKey {
let arg_name: Ident = format_ident!("_{}", idx);
let ty_path = type_gen
.resolve_type_path(id)
.expect("type is in metadata; qed");
let alias_name = format_ident!("Param{}", idx);
let alias_type = ty_path.to_token_stream(type_gen.settings());
let alias_type_def = quote!( pub type #alias_name = #alias_type; );
let alias_type_path = quote!( types::#alias_module_name::#alias_name );
MapEntryKey {
arg_name,
alias_type_def,
alias_type_path,
hasher,
}
};
let keys: Vec<MapEntryKey> = match storage_entry.entry_type() {
StorageEntryType::Plain(_) => vec![],
StorageEntryType::Map {
key_ty, hashers, ..
} => {
if hashers.len() == 1 {
// If there's exactly 1 hasher, then we have a plain StorageMap. We can't
// break the key down (even if it's a tuple) because the hasher applies to
// the whole key.
vec![map_entry_key(0, *key_ty, hashers[0])]
} else {
// If there are multiple hashers, then we have a StorageDoubleMap or StorageNMap.
// We expect the key type to be tuple, and we will return a MapEntryKey for each
// key in the tuple.
let hasher_count = hashers.len();
let tuple = match &type_gen
.resolve_type(*key_ty)
.expect("key type should be present")
.type_def
{
TypeDef::Tuple(tuple) => tuple,
_ => {
return Err(CodegenError::InvalidStorageHasherCount {
storage_entry_name: storage_entry.name().to_owned(),
key_count: 1,
hasher_count,
});
}
};
// We should have the same number of hashers and keys.
let key_count = tuple.fields.len();
if hasher_count != key_count {
return Err(CodegenError::InvalidStorageHasherCount {
storage_entry_name: storage_entry.name().to_owned(),
key_count,
hasher_count,
});
}
// Collect them together.
tuple
.fields
.iter()
.zip(hashers)
.enumerate()
.map(|(idx, (field, hasher))| map_entry_key(idx, field.id, *hasher))
.collect()
}
}
};
let types_mod_ident = type_gen.types_mod_ident();
let pallet_name = pallet.name();
let storage_name = storage_entry.name();
let Some(storage_hash) = pallet.storage_hash(storage_name) else {
let storage_entry_name_str = storage_entry.name();
let storage_entry_snake_case_name = storage_entry_name_str.to_snake_case();
let storage_entry_snake_case_ident = format_ident!("{storage_entry_snake_case_name}");
let Some(validation_hash) = pallet.storage_hash(storage_entry_name_str) else {
return Err(CodegenError::MissingStorageMetadata(
pallet_name.into(),
storage_name.into(),
storage_entry_name_str.into(),
));
};
let docs = storage_entry.docs();
let docs = type_gen
let docs: TokenStream2 = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let is_defaultable_type = match storage_entry.modifier() {
StorageEntryModifier::Default => quote!(#crate_path::utils::Yes),
StorageEntryModifier::Optional => quote!(()),
struct Input {
type_alias: syn::Ident,
type_path: TokenStream2,
}
let storage_key_types: Vec<Input> = storage_entry
.keys()
.enumerate()
.map(|(idx, key)| {
// Storage key aliases are just indexes; no names to use.
let type_alias = format_ident!("Param{}", idx);
// Path to the actual type we'll have generated for this input.
let type_path = type_gen
.resolve_type_path(key.key_id)
.expect("view function input type is in metadata; qed")
.to_token_stream(type_gen.settings());
Input {
type_alias,
type_path,
}
})
.collect();
let storage_key_tuple_types = storage_key_types
.iter()
.map(|i| {
let ty = &i.type_alias;
quote!(#storage_entry_snake_case_ident::#ty)
})
.collect::<Vec<_>>();
let storage_key_type_aliases = storage_key_types
.iter()
.map(|i| {
let ty = &i.type_alias;
let path = &i.type_path;
quote!(pub type #ty = #path;)
})
.collect::<Vec<_>>();
let storage_value_type_path = type_gen
.resolve_type_path(storage_entry.value_ty())?
.to_token_stream(type_gen.settings());
let is_plain = if storage_entry.keys().len() == 0 {
quote!(#crate_path::utils::Yes)
} else {
quote!(#crate_path::utils::Maybe)
};
// Note: putting `#crate_path::storage::address::StaticStorageKey` into this variable is necessary
// to get the line width below a certain limit. If not done, rustfmt will refuse to format the following big expression.
// for more information see [this post](https://users.rust-lang.org/t/rustfmt-silently-fails-to-work/75485/4).
let static_storage_key: TokenStream = quote!(#crate_path::storage::address::StaticStorageKey);
let all_fns = (0..=keys.len()).map(|n_keys| {
let keys_slice = &keys[..n_keys];
let (fn_name, is_fetchable, is_iterable) = if n_keys == keys.len() {
let fn_name = format_ident!("{snake_case_name}");
(fn_name, true, false)
} else {
let fn_name = if n_keys == 0 {
format_ident!("{snake_case_name}_iter")
} else {
format_ident!("{snake_case_name}_iter{}", n_keys)
};
(fn_name, false, true)
};
let is_fetchable_type = is_fetchable
.then_some(quote!(#crate_path::utils::Yes))
.unwrap_or(quote!(()));
let is_iterable_type = is_iterable
.then_some(quote!(#crate_path::utils::Yes))
.unwrap_or(quote!(()));
let (keys, keys_type) = match keys_slice.len() {
0 => (quote!(()), quote!(())),
1 => {
let key = &keys_slice[0];
if key.hasher.ends_with_key() {
let arg = &key.arg_name;
let keys = quote!(#static_storage_key::new(#arg));
let path = &key.alias_type_path;
let path = quote!(#static_storage_key<#path>);
(keys, path)
} else {
(quote!(()), quote!(()))
}
}
_ => {
let keys_iter = keys_slice.iter().map(
|MapEntryKey {
arg_name, hasher, ..
}| {
if hasher.ends_with_key() {
quote!( #static_storage_key::new(#arg_name) )
} else {
quote!(())
}
},
);
let keys = quote!( (#(#keys_iter,)*) );
let paths_iter = keys_slice.iter().map(
|MapEntryKey {
alias_type_path,
hasher,
..
}| {
if hasher.ends_with_key() {
quote!( #static_storage_key<#alias_type_path> )
} else {
quote!(())
}
},
);
let paths = quote!( (#(#paths_iter,)*) );
(keys, paths)
}
};
let key_args = keys_slice.iter().map(
|MapEntryKey {
arg_name,
alias_type_path,
..
}| quote!( #arg_name: #alias_type_path ),
);
quote!(
#docs
pub fn #fn_name(
&self,
#(#key_args,)*
) -> #crate_path::storage::address::StaticAddress::<
#keys_type,
#alias_storage_path,
#is_fetchable_type,
#is_defaultable_type,
#is_iterable_type
> {
#crate_path::storage::address::StaticAddress::new_static(
#pallet_name,
#storage_name,
#keys,
[#(#storage_hash,)*]
)
}
)
});
let alias_types = keys
.iter()
.map(|MapEntryKey { alias_type_def, .. }| alias_type_def);
let types_mod_ident = type_gen.types_mod_ident();
// Generate type alias for the return type only, since
// the keys of the storage entry are not explicitly named.
let alias_module = quote! {
pub mod #alias_module_name {
let storage_entry_types = quote!(
pub mod #storage_entry_snake_case_ident {
use super::root_mod;
use super::#types_mod_ident;
pub type #alias_name = #storage_entry_value_ty;
#(#storage_key_type_aliases)*
#( #alias_types )*
pub mod output {
use super::#types_mod_ident;
pub type Output = #storage_value_type_path;
}
}
};
);
Ok((
quote! {
#( #all_fns )*
},
alias_module,
))
let storage_entry_method = quote!(
#docs
pub fn #storage_entry_snake_case_ident(&self) -> #crate_path::storage::address::StaticAddress<
(#(#storage_key_tuple_types,)*),
#storage_entry_snake_case_ident::output::Output,
#is_plain
> {
#crate_path::storage::address::StaticAddress::new_static(
#pallet_name,
#storage_entry_name_str,
[#(#validation_hash,)*],
)
}
);
Ok((storage_entry_types, storage_entry_method))
}
#[cfg(test)]
+2
View File
@@ -27,6 +27,8 @@ std = [
"tracing/std",
"impl-serde/std",
"primitive-types/std",
"sp-core/std",
"sp-keyring/std",
"sp-crypto-hashing/std",
]
@@ -7,7 +7,9 @@ use crate::config::transaction_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
};
use crate::dynamic::Value;
use crate::{Metadata, config::Config, error::Error};
use crate::error::ExtrinsicError;
use crate::{Metadata, config::Config};
use alloc::borrow::ToOwned;
use frame_decode::extrinsics::ExtrinsicExtensions;
use scale_decode::DecodeAsType;
@@ -50,7 +52,7 @@ impl<'a, T: Config> ExtrinsicTransactionExtensions<'a, T> {
/// Searches through all signed extensions to find a specific one.
/// If the Signed Extension is not found `Ok(None)` is returned.
/// If the Signed Extension is found but decoding failed `Err(_)` is returned.
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
pub fn find<S: TransactionExtension<T>>(&self) -> Result<Option<S::Decoded>, ExtrinsicError> {
for ext in self.iter() {
match ext.as_signed_extension::<S>() {
// We found a match; return it:
@@ -117,12 +119,16 @@ impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
}
/// Signed Extension as a [`scale_value::Value`]
pub fn value(&self) -> Result<Value<u32>, Error> {
pub fn value(&self) -> Result<Value<u32>, ExtrinsicError> {
let value = scale_value::scale::decode_as_type(
&mut &self.bytes[..],
self.ty_id,
self.metadata.types(),
)?;
)
.map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension {
name: self.identifier.to_owned(),
error: e.into(),
})?;
Ok(value)
}
@@ -131,15 +137,19 @@ impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
/// decode with.
pub fn as_signed_extension<S: TransactionExtension<T>>(
&self,
) -> Result<Option<S::Decoded>, Error> {
) -> Result<Option<S::Decoded>, ExtrinsicError> {
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
return Ok(None);
}
self.as_type::<S::Decoded>().map(Some)
}
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
fn as_type<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())
.map_err(|e| ExtrinsicError::CouldNotDecodeTransactionExtension {
name: self.identifier.to_owned(),
error: e,
})?;
Ok(value)
}
}
+81 -95
View File
@@ -2,19 +2,16 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::BlockError;
use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use crate::{
Metadata,
config::{Config, HashFor, Hasher},
error::{Error, MetadataError},
error::{ExtrinsicDecodeErrorAt, ExtrinsicDecodeErrorAtReason, ExtrinsicError},
};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::ops::Deref;
use frame_decode::extrinsics::Extrinsic;
use scale_decode::DecodeAsType;
use subxt_metadata::PalletMetadata;
use scale_decode::{DecodeAsFields, DecodeAsType};
pub use crate::blocks::StaticExtrinsic;
@@ -30,7 +27,10 @@ impl<T: Config> Extrinsics<T> {
/// Instantiate a new [`Extrinsics`] object, given a vector containing
/// each extrinsic hash (in the form of bytes) and some metadata that
/// we'll use to decode them.
pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, Error> {
pub fn decode_from(
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
) -> Result<Self, ExtrinsicDecodeErrorAt> {
let hasher = T::Hasher::new(&metadata);
let extrinsics = extrinsics
.into_iter()
@@ -39,29 +39,25 @@ impl<T: Config> Extrinsics<T> {
let cursor = &mut &*bytes;
// Try to decode the extrinsic.
let decoded_info = frame_decode::extrinsics::decode_extrinsic(
cursor,
metadata.deref(),
metadata.types(),
)
.map_err(|error| BlockError::ExtrinsicDecodeError {
extrinsic_index,
error,
})?
.into_owned();
let decoded_info =
frame_decode::extrinsics::decode_extrinsic(cursor, &metadata, metadata.types())
.map_err(|error| ExtrinsicDecodeErrorAt {
extrinsic_index,
error: ExtrinsicDecodeErrorAtReason::DecodeError(error),
})?
.into_owned();
// We didn't consume all bytes, so decoding probably failed.
if !cursor.is_empty() {
return Err(BlockError::LeftoverBytes {
return Err(ExtrinsicDecodeErrorAt {
extrinsic_index,
num_leftover_bytes: cursor.len(),
}
.into());
error: ExtrinsicDecodeErrorAtReason::LeftoverBytes(cursor.to_vec()),
});
}
Ok(Arc::new((decoded_info, bytes)))
})
.collect::<Result<_, Error>>()?;
.collect::<Result<_, ExtrinsicDecodeErrorAt>>()?;
Ok(Self {
extrinsics,
@@ -106,7 +102,7 @@ impl<T: Config> Extrinsics<T> {
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<E: StaticExtrinsic>(
&self,
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, Error>> {
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, ExtrinsicError>> {
self.iter().filter_map(|details| {
match details.as_extrinsic::<E>() {
// Failed to decode extrinsic:
@@ -120,18 +116,22 @@ impl<T: Config> Extrinsics<T> {
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the first extrinsic found which decodes to the provided `E` type.
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
pub fn find_first<E: StaticExtrinsic>(
&self,
) -> Result<Option<FoundExtrinsic<T, E>>, ExtrinsicError> {
self.find::<E>().next().transpose()
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
pub fn find_last<E: StaticExtrinsic>(
&self,
) -> Result<Option<FoundExtrinsic<T, E>>, ExtrinsicError> {
self.find::<E>().last().transpose()
}
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, ExtrinsicError> {
Ok(self.find::<E>().next().transpose()?.is_some())
}
}
@@ -264,61 +264,63 @@ where
}
/// The index of the extrinsic variant that the extrinsic originated from.
pub fn variant_index(&self) -> u8 {
pub fn call_index(&self) -> u8 {
self.decoded_info().call_index()
}
/// The name of the pallet from whence the extrinsic originated.
pub fn pallet_name(&self) -> Result<&str, Error> {
Ok(self.extrinsic_metadata()?.pallet.name())
pub fn pallet_name(&self) -> &str {
self.decoded_info().pallet_name()
}
/// The name of the call (ie the name of the variant that it corresponds to).
pub fn variant_name(&self) -> Result<&str, Error> {
Ok(&self.extrinsic_metadata()?.variant.name)
}
/// Fetch the metadata for this extrinsic.
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
let variant = pallet
.call_variant_by_index(self.variant_index())
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
Ok(ExtrinsicMetadataDetails { pallet, variant })
pub fn call_name(&self) -> &str {
self.decoded_info().call_name()
}
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were present in the extrinsic.
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
let bytes = &mut self.field_bytes();
let extrinsic_metadata = self.extrinsic_metadata()?;
let mut fields = extrinsic_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let mut fields = self.decoded_info().call_data().map(|d| {
let name = if d.name().is_empty() {
None
} else {
Some(d.name())
};
scale_decode::Field::new(*d.ty(), name)
});
let decoded =
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| {
ExtrinsicError::CannotDecodeFields {
extrinsic_index: self.index as usize,
error: e,
}
})?;
Ok(decoded)
}
/// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
/// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
let extrinsic_metadata = self.extrinsic_metadata()?;
if extrinsic_metadata.pallet.name() == E::PALLET
&& extrinsic_metadata.variant.name == E::CALL
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, ExtrinsicError> {
if self.decoded_info().pallet_name() == E::PALLET
&& self.decoded_info().call_name() == E::CALL
{
let mut fields = extrinsic_metadata
.variant
.fields
.iter()
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let mut fields = self.decoded_info().call_data().map(|d| {
let name = if d.name().is_empty() {
None
} else {
Some(d.name())
};
scale_decode::Field::new(*d.ty(), name)
});
let decoded =
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())
.map_err(|e| ExtrinsicError::CannotDecodeFields {
extrinsic_index: self.index as usize,
error: e,
})?;
Ok(Some(decoded))
} else {
Ok(None)
@@ -328,12 +330,16 @@ where
/// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum type (which includes
/// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
/// type for this is exposed via static codegen as a root level `Call` type.
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
let decoded = E::decode_as_type(
&mut &self.call_bytes()[..],
self.metadata.outer_enums().call_enum_ty(),
self.metadata.types(),
)?;
)
.map_err(|e| ExtrinsicError::CannotDecodeIntoRootExtrinsic {
extrinsic_index: self.index as usize,
error: e,
})?;
Ok(decoded)
}
@@ -351,14 +357,6 @@ pub struct FoundExtrinsic<T: Config, E> {
pub value: E,
}
/// Details for the given extrinsic plucked from the metadata.
pub struct ExtrinsicMetadataDetails<'a> {
/// Metadata for the pallet that the extrinsic belongs to.
pub pallet: PalletMetadata<'a>,
/// Metadata for the variant which describes the pallet extrinsics.
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
}
#[cfg(test)]
mod tests {
use super::*;
@@ -488,7 +486,7 @@ mod tests {
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
Metadata::from(metadata)
metadata
}
#[test]
@@ -513,12 +511,10 @@ mod tests {
let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![]], metadata);
assert_matches!(
result.err(),
Some(crate::Error::Block(
crate::error::BlockError::ExtrinsicDecodeError {
extrinsic_index: 0,
error: _
}
))
Some(crate::error::ExtrinsicDecodeErrorAt {
extrinsic_index: 0,
error: _
})
);
}
@@ -533,12 +529,12 @@ mod tests {
assert_matches!(
result.err(),
Some(crate::Error::Block(
crate::error::BlockError::ExtrinsicDecodeError {
extrinsic_index: 0,
error: ExtrinsicDecodeError::VersionNotSupported(3),
}
))
Some(crate::error::ExtrinsicDecodeErrorAt {
extrinsic_index: 0,
error: ExtrinsicDecodeErrorAtReason::DecodeError(
ExtrinsicDecodeError::VersionNotSupported(3)
),
})
);
}
@@ -611,20 +607,10 @@ mod tests {
assert_eq!(extrinsic.index(), 0);
assert_eq!(extrinsic.pallet_index(), 0);
assert_eq!(
extrinsic
.pallet_name()
.expect("Valid metadata contains pallet name"),
"Test"
);
assert_eq!(extrinsic.pallet_name(), "Test");
assert_eq!(extrinsic.variant_index(), 2);
assert_eq!(
extrinsic
.variant_name()
.expect("Valid metadata contains variant name"),
"TestCall"
);
assert_eq!(extrinsic.call_index(), 2);
assert_eq!(extrinsic.call_name(), "TestCall");
// Decode the extrinsic to the root enum.
let decoded_extrinsic = extrinsic
+10 -11
View File
@@ -15,7 +15,7 @@
//!
//! use subxt_macro::subxt;
//! use subxt_core::blocks;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//! use subxt_core::config::PolkadotConfig;
//! use alloc::vec;
//!
@@ -28,7 +28,7 @@
//!
//! // Some metadata we'd like to use to help us decode extrinsics:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Some extrinsics we'd like to decode:
//! let ext_bytes = vec![
@@ -45,14 +45,14 @@
//!
//! // We can iterate over them and decode various details out of them.
//! for ext in exts.iter() {
//! println!("Pallet: {}", ext.pallet_name().unwrap());
//! println!("Call: {}", ext.variant_name().unwrap());
//! println!("Pallet: {}", ext.pallet_name());
//! println!("Call: {}", ext.call_name());
//! }
//!
//! # let ext_details: Vec<_> = exts.iter()
//! # .map(|ext| {
//! # let pallet = ext.pallet_name().unwrap().to_string();
//! # let call = ext.variant_name().unwrap().to_string();
//! # let pallet = ext.pallet_name().to_string();
//! # let call = ext.call_name().to_string();
//! # (pallet, call)
//! # })
//! # .collect();
@@ -70,14 +70,13 @@ mod static_extrinsic;
use crate::Metadata;
use crate::config::Config;
use crate::error::Error;
use crate::error::ExtrinsicDecodeErrorAt;
pub use crate::error::ExtrinsicError;
use alloc::vec::Vec;
pub use crate::error::BlockError;
pub use extrinsic_transaction_extensions::{
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
};
pub use extrinsics::{ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic};
pub use extrinsics::{ExtrinsicDetails, Extrinsics, FoundExtrinsic};
pub use static_extrinsic::StaticExtrinsic;
/// Instantiate a new [`Extrinsics`] object, given a vector containing each extrinsic hash (in the
@@ -87,6 +86,6 @@ pub use static_extrinsic::StaticExtrinsic;
pub fn decode_from<T: Config>(
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
) -> Result<Extrinsics<T>, Error> {
) -> Result<Extrinsics<T>, ExtrinsicDecodeErrorAt> {
Extrinsics::decode_from(extrinsics, metadata)
}
+1 -1
View File
@@ -5,8 +5,8 @@
//! A couple of client types that we use elsewhere.
use crate::{
Metadata,
config::{Config, HashFor},
metadata::Metadata,
};
use derive_where::derive_where;
+47 -13
View File
@@ -4,17 +4,16 @@
//! Construct addresses to access constants with.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
use alloc::string::String;
use derive_where::derive_where;
use scale_decode::DecodeAsType;
/// This represents a constant address. Anything implementing this trait
/// can be used to fetch constants.
pub trait Address {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
type Target: DecodeAsType;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
@@ -30,22 +29,54 @@ pub trait Address {
}
}
// Any reference to an address is a valid address.
impl<A: Address + ?Sized> Address for &'_ A {
type Target = A::Target;
fn pallet_name(&self) -> &str {
A::pallet_name(*self)
}
fn constant_name(&self) -> &str {
A::constant_name(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
A::validation_hash(*self)
}
}
// (str, str) and similar are valid addresses.
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
type Target = scale_value::Value;
fn pallet_name(&self) -> &str {
self.0.as_ref()
}
fn constant_name(&self) -> &str {
self.1.as_ref()
}
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// This represents the address of a constant.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct DefaultAddress<ReturnTy> {
pub struct StaticAddress<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<ReturnTy>,
}
/// The type of address used by our static codegen.
pub type StaticAddress<ReturnTy> = DefaultAddress<ReturnTy>;
/// The type of address typically used to return dynamic constant values.
pub type DynamicAddress = DefaultAddress<DecodedValueThunk>;
/// A dynamic lookup address to access a constant.
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy>;
impl<ReturnTy> DefaultAddress<ReturnTy> {
/// Create a new [`DefaultAddress`] to use to look up a constant.
impl<ReturnTy> StaticAddress<ReturnTy> {
/// Create a new [`StaticAddress`] to use to look up a constant.
pub fn new(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
@@ -55,7 +86,7 @@ impl<ReturnTy> DefaultAddress<ReturnTy> {
}
}
/// Create a new [`DefaultAddress`] that will be validated
/// Create a new [`StaticAddress`] that will be validated
/// against node metadata using the hash given.
#[doc(hidden)]
pub fn new_static(
@@ -82,7 +113,7 @@ impl<ReturnTy> DefaultAddress<ReturnTy> {
}
}
impl<ReturnTy: DecodeWithMetadata> Address for DefaultAddress<ReturnTy> {
impl<ReturnTy: DecodeAsType> Address for StaticAddress<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
@@ -99,6 +130,9 @@ impl<ReturnTy: DecodeWithMetadata> Address for DefaultAddress<ReturnTy> {
}
/// Construct a new dynamic constant lookup.
pub fn dynamic(pallet_name: impl Into<String>, constant_name: impl Into<String>) -> DynamicAddress {
pub fn dynamic<ReturnTy: DecodeAsType>(
pallet_name: impl Into<String>,
constant_name: impl Into<String>,
) -> DynamicAddress<ReturnTy> {
DynamicAddress::new(pallet_name, constant_name)
}
+44 -20
View File
@@ -12,7 +12,7 @@
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::constants;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -23,7 +23,7 @@
//!
//! // Some metadata we'd like to access constants in:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // We can use a static address to obtain some constant:
//! let address = polkadot::constants().balances().existential_deposit();
@@ -40,26 +40,32 @@
pub mod address;
use crate::Metadata;
use crate::error::ConstantError;
use address::Address;
use alloc::borrow::ToOwned;
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
use alloc::string::ToString;
use alloc::vec::Vec;
use frame_decode::constants::ConstantTypeInfo;
use scale_decode::IntoVisitor;
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the constant value is the same as the shape expected by the static address.
///
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
/// shape of the constant value), this just returns `Ok(())`
pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), ConstantError> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = metadata
.pallet_by_name_err(address.pallet_name())?
.pallet_by_name(address.pallet_name())
.ok_or_else(|| ConstantError::PalletNameNotFound(address.pallet_name().to_string()))?
.constant_hash(address.constant_name())
.ok_or_else(|| {
MetadataError::ConstantNameNotFound(address.constant_name().to_owned())
.ok_or_else(|| ConstantError::ConstantNameNotFound {
pallet_name: address.pallet_name().to_string(),
constant_name: address.constant_name().to_owned(),
})?;
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
return Err(ConstantError::IncompatibleCodegen);
}
}
Ok(())
@@ -67,19 +73,37 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
/// Fetch a constant out of the metadata given a constant address. If the `address` has been
/// statically generated, this will validate that the constant shape is as expected, too.
pub fn get<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<Addr::Target, Error> {
pub fn get<Addr: Address>(
address: Addr,
metadata: &Metadata,
) -> Result<Addr::Target, ConstantError> {
// 1. Validate constant shape if hash given:
validate(address, metadata)?;
validate(&address, metadata)?;
// 2. Attempt to decode the constant into the type given:
let constant = metadata
.pallet_by_name_err(address.pallet_name())?
.constant_by_name(address.constant_name())
.ok_or_else(|| MetadataError::ConstantNameNotFound(address.constant_name().to_owned()))?;
let value = <Addr::Target as DecodeWithMetadata>::decode_with_metadata(
&mut constant.value(),
constant.ty(),
let constant = frame_decode::constants::decode_constant(
address.pallet_name(),
address.constant_name(),
metadata,
)?;
Ok(value)
metadata.types(),
Addr::Target::into_visitor(),
)
.map_err(ConstantError::CouldNotDecodeConstant)?;
Ok(constant)
}
/// Access the bytes of a constant by the address it is registered under.
pub fn get_bytes<Addr: Address>(
address: Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, ConstantError> {
// 1. Validate custom value shape if hash given:
validate(&address, metadata)?;
// 2. Return the underlying bytes:
let constant = metadata
.constant_info(address.pallet_name(), address.constant_name())
.map_err(|e| ConstantError::ConstantInfoError(e.into_owned()))?;
Ok(constant.bytes.to_vec())
}
+54 -19
View File
@@ -4,22 +4,23 @@
//! Construct addresses to access custom values with.
use crate::dynamic::DecodedValueThunk;
use crate::metadata::DecodeWithMetadata;
use alloc::borrow::Cow;
use alloc::string::String;
use derive_where::derive_where;
use scale_decode::DecodeAsType;
/// Use this with [`Address::IsDecodable`].
pub use crate::utils::Yes;
pub use crate::utils::{Maybe, No, NoMaybe};
/// This represents the address of a custom value in the metadata.
/// Anything that implements it can be used to fetch custom values from the metadata.
/// The trait is implemented by [`str`] for dynamic lookup and [`StaticAddress`] for static queries.
pub trait Address {
/// The type of the custom value.
type Target: DecodeWithMetadata;
type Target: DecodeAsType;
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
/// Should be `()` for custom values, that have an invalid type id.
type IsDecodable;
/// Should be `No` for custom values, that have an invalid type id.
type IsDecodable: NoMaybe;
/// the name (key) by which the custom value can be accessed in the metadata.
fn name(&self) -> &str;
@@ -30,9 +31,24 @@ pub trait Address {
}
}
// Any reference to an address is a valid address
impl<A: Address + ?Sized> Address for &'_ A {
type Target = A::Target;
type IsDecodable = A::IsDecodable;
fn name(&self) -> &str {
A::name(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
A::validation_hash(*self)
}
}
// Support plain strings for looking up custom values.
impl Address for str {
type Target = DecodedValueThunk;
type IsDecodable = Yes;
type Target = scale_value::Value;
type IsDecodable = Maybe;
fn name(&self) -> &str {
self
@@ -42,19 +58,31 @@ impl Address for str {
/// A static address to a custom value.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct StaticAddress<ReturnTy, IsDecodable> {
name: &'static str,
name: Cow<'static, str>,
hash: Option<[u8; 32]>,
phantom: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
marker: core::marker::PhantomData<(ReturnTy, IsDecodable)>,
}
/// A dynamic address to a custom value.
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy, Maybe>;
impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
#[doc(hidden)]
/// Creates a new StaticAddress.
pub fn new_static(name: &'static str, hash: [u8; 32]) -> StaticAddress<ReturnTy, IsDecodable> {
StaticAddress::<ReturnTy, IsDecodable> {
name,
pub fn new_static(name: &'static str, hash: [u8; 32]) -> Self {
Self {
name: Cow::Borrowed(name),
hash: Some(hash),
phantom: core::marker::PhantomData,
marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticAddress`]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into().into(),
hash: None,
marker: core::marker::PhantomData,
}
}
@@ -63,20 +91,27 @@ impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
Self {
name: self.name,
hash: None,
phantom: self.phantom,
marker: self.marker,
}
}
}
impl<R: DecodeWithMetadata, Y> Address for StaticAddress<R, Y> {
type Target = R;
type IsDecodable = Y;
impl<Target: DecodeAsType, IsDecodable: NoMaybe> Address for StaticAddress<Target, IsDecodable> {
type Target = Target;
type IsDecodable = IsDecodable;
fn name(&self) -> &str {
self.name
&self.name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.hash
}
}
/// Construct a new dynamic custom value lookup.
pub fn dynamic<ReturnTy: DecodeAsType>(
custom_value_name: impl Into<String>,
) -> DynamicAddress<ReturnTy> {
DynamicAddress::new(custom_value_name)
}
+32 -28
View File
@@ -12,7 +12,7 @@
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::custom_values;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -23,7 +23,7 @@
//!
//! // Some metadata we'd like to access custom values in:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // At the moment, we don't expect to see any custom values in the metadata
//! // for Polkadot, so this will return an error:
@@ -32,61 +32,64 @@
pub mod address;
use crate::utils::Yes;
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
use crate::utils::Maybe;
use crate::{Metadata, error::CustomValueError};
use address::Address;
use alloc::vec::Vec;
use frame_decode::custom_values::CustomValueTypeInfo;
use scale_decode::IntoVisitor;
/// Run the validation logic against some custom value address you'd like to access. Returns `Ok(())`
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Returns an error if the address was not valid (wrong name, type or raw bytes)
pub fn validate<Addr: Address + ?Sized>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), CustomValueError> {
if let Some(actual_hash) = address.validation_hash() {
let custom = metadata.custom();
let custom_value = custom
.get(address.name())
.ok_or_else(|| MetadataError::CustomValueNameNotFound(address.name().into()))?;
.ok_or_else(|| CustomValueError::NotFound(address.name().into()))?;
let expected_hash = custom_value.hash();
if actual_hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
return Err(CustomValueError::IncompatibleCodegen);
}
}
if metadata.custom().get(address.name()).is_none() {
return Err(MetadataError::IncompatibleCodegen.into());
}
Ok(())
}
/// Access a custom value by the address it is registered under. This can be just a [str] to get back a dynamic value,
/// or a static address from the generated static interface to get a value of a static type returned.
pub fn get<Addr: Address<IsDecodable = Yes> + ?Sized>(
address: &Addr,
pub fn get<Addr: Address<IsDecodable = Maybe>>(
address: Addr,
metadata: &Metadata,
) -> Result<Addr::Target, Error> {
) -> Result<Addr::Target, CustomValueError> {
// 1. Validate custom value shape if hash given:
validate(address, metadata)?;
validate(&address, metadata)?;
// 2. Attempt to decode custom value:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
let value = <Addr::Target as DecodeWithMetadata>::decode_with_metadata(
&mut custom_value.bytes(),
custom_value.type_id(),
let value = frame_decode::custom_values::decode_custom_value(
address.name(),
metadata,
)?;
metadata.types(),
Addr::Target::into_visitor(),
)
.map_err(CustomValueError::CouldNotDecodeCustomValue)?;
Ok(value)
}
/// Access the bytes of a custom value by the address it is registered under.
pub fn get_bytes<Addr: Address + ?Sized>(
address: &Addr,
pub fn get_bytes<Addr: Address>(
address: Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
) -> Result<Vec<u8>, CustomValueError> {
// 1. Validate custom value shape if hash given:
validate(address, metadata)?;
validate(&address, metadata)?;
// 2. Return the underlying bytes:
let custom_value = metadata.custom_value_by_name_err(address.name())?;
Ok(custom_value.bytes().to_vec())
let custom_value = metadata
.custom_value_info(address.name())
.map_err(|e| CustomValueError::NotFound(e.not_found))?;
Ok(custom_value.bytes.to_vec())
}
#[cfg(test)]
@@ -154,7 +157,7 @@ mod tests {
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
Metadata::from(metadata)
metadata
}
#[test]
@@ -162,8 +165,9 @@ mod tests {
let metadata = mock_metadata();
assert!(custom_values::get("Invalid Address", &metadata).is_err());
let person_decoded_value_thunk = custom_values::get("Mr. Robot", &metadata).unwrap();
let person: Person = person_decoded_value_thunk.as_type().unwrap();
let person_addr = custom_values::address::dynamic::<Person>("Mr. Robot");
let person = custom_values::get(&person_addr, &metadata).unwrap();
assert_eq!(
person,
Person {
+2 -62
View File
@@ -5,17 +5,8 @@
//! This module provides the entry points to create dynamic
//! transactions, storage and constant lookups.
use crate::metadata::{DecodeWithMetadata, Metadata};
use alloc::vec::Vec;
use scale_decode::DecodeAsType;
pub use scale_value::{At, 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<u32>;
// Submit dynamic transactions.
pub use crate::tx::payload::dynamic as tx;
@@ -31,56 +22,5 @@ pub use crate::runtime_api::payload::dynamic as runtime_api_call;
// Execute View Function API function call dynamically.
pub use crate::view_functions::payload::dynamic as view_function_call;
/// This is the result of making a dynamic request to a node. From this,
/// we can return the raw SCALE bytes that we were handed back, or we can
/// complete the decoding of the bytes into a [`DecodedValue`] type.
pub struct DecodedValueThunk {
type_id: u32,
metadata: Metadata,
scale_bytes: Vec<u8>,
}
impl DecodeWithMetadata for DecodedValueThunk {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<Self, scale_decode::Error> {
let mut v = Vec::with_capacity(bytes.len());
v.extend_from_slice(bytes);
*bytes = &[];
Ok(DecodedValueThunk {
type_id,
metadata: metadata.clone(),
scale_bytes: v,
})
}
}
impl DecodedValueThunk {
/// Return the SCALE encoded bytes handed back from the node.
pub fn into_encoded(self) -> Vec<u8> {
self.scale_bytes
}
/// Return the SCALE encoded bytes handed back from the node without taking ownership of them.
pub fn encoded(&self) -> &[u8] {
&self.scale_bytes
}
/// Decode the SCALE encoded storage entry into a dynamic [`DecodedValue`] type.
pub fn to_value(&self) -> Result<DecodedValue, scale_decode::Error> {
let val = scale_value::scale::decode_as_type(
&mut &*self.scale_bytes,
self.type_id,
self.metadata.types(),
)?;
Ok(val)
}
/// decode the `DecodedValueThunk` into a concrete type.
pub fn as_type<T: DecodeAsType>(&self) -> Result<T, scale_decode::Error> {
T::decode_as_type(
&mut &self.scale_bytes[..],
self.type_id,
self.metadata.types(),
)
}
}
/// Obtain a custom value from the metadata.
pub use crate::custom_values::address::dynamic as custom_value;
+248 -156
View File
@@ -6,53 +6,238 @@
use alloc::boxed::Box;
use alloc::string::String;
use subxt_metadata::StorageHasher;
use alloc::vec::Vec;
use thiserror::Error as DeriveError;
/// The error emitted when something goes wrong.
#[derive(Debug, DeriveError)]
#[allow(missing_docs)]
pub enum Error {
/// Codec error.
#[error("Codec error: {0}")]
Codec(codec::Error),
/// Metadata error.
#[error(transparent)]
Metadata(#[from] MetadataError),
/// Storage address error.
StorageError(#[from] StorageError),
#[error(transparent)]
StorageAddress(#[from] StorageAddressError),
/// Error decoding to a [`crate::dynamic::Value`].
#[error("Error decoding into dynamic value: {0}")]
Decode(#[from] scale_decode::Error),
/// Error encoding from a [`crate::dynamic::Value`].
#[error("Error encoding from dynamic value: {0}")]
Encode(#[from] scale_encode::Error),
/// Error constructing an extrinsic.
#[error("Error constructing transaction: {0}")]
Extrinsic(#[from] ExtrinsicError),
/// Block body error.
#[error("Error working with block_body: {0}")]
Block(#[from] BlockError),
#[error(transparent)]
Constant(#[from] ConstantError),
#[error(transparent)]
CustomValue(#[from] CustomValueError),
#[error(transparent)]
RuntimeApi(#[from] RuntimeApiError),
#[error(transparent)]
ViewFunction(#[from] ViewFunctionError),
#[error(transparent)]
Events(#[from] EventsError),
}
impl From<scale_decode::visitor::DecodeError> for Error {
fn from(err: scale_decode::visitor::DecodeError) -> Error {
Error::Decode(err.into())
}
}
// TODO: when `codec::Error` implements `core::Error`
// remove this impl and replace it by thiserror #[from]
impl From<codec::Error> for Error {
fn from(err: codec::Error) -> Error {
Error::Codec(err)
}
}
/// Block error
#[derive(Debug, DeriveError)]
pub enum BlockError {
/// Leftover bytes found after decoding the extrinsic.
#[non_exhaustive]
#[allow(missing_docs)]
pub enum EventsError {
#[error("Can't decode event: can't decode phase: {0}")]
CannotDecodePhase(codec::Error),
#[error("Can't decode event: can't decode pallet index: {0}")]
CannotDecodePalletIndex(codec::Error),
#[error("Can't decode event: can't decode variant index: {0}")]
CannotDecodeVariantIndex(codec::Error),
#[error("Can't decode event: can't find pallet with index {0}")]
CannotFindPalletWithIndex(u8),
#[error(
"Can't decode event: can't find variant with index {variant_index} in pallet {pallet_name}"
)]
CannotFindVariantWithIndex {
pallet_name: String,
variant_index: u8,
},
#[error("Can't decode field {field_name:?} in event {pallet_name}.{event_name}: {reason}")]
CannotDecodeFieldInEvent {
pallet_name: String,
event_name: String,
field_name: String,
reason: scale_decode::visitor::DecodeError,
},
#[error("Can't decode event topics: {0}")]
CannotDecodeEventTopics(codec::Error),
#[error("Can't decode the fields of event {pallet_name}.{event_name}: {reason}")]
CannotDecodeEventFields {
pallet_name: String,
event_name: String,
reason: scale_decode::Error,
},
#[error("Can't decode event {pallet_name}.{event_name} to Event enum: {reason}")]
CannotDecodeEventEnum {
pallet_name: String,
event_name: String,
reason: scale_decode::Error,
},
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ViewFunctionError {
#[error("The static View Function address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find View Function: pallet {0} not found")]
PalletNotFound(String),
#[error("Can't find View Function {function_name} in pallet {pallet_name}")]
ViewFunctionNotFound {
pallet_name: String,
function_name: String,
},
#[error("Failed to encode View Function inputs: {0}")]
CouldNotEncodeInputs(frame_decode::view_functions::ViewFunctionInputsEncodeError),
#[error("Failed to decode View Function: {0}")]
CouldNotDecodeResponse(frame_decode::view_functions::ViewFunctionDecodeError<u32>),
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum RuntimeApiError {
#[error("The static Runtime API address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Runtime API trait not found: {0}")]
TraitNotFound(String),
#[error("Runtime API method {method_name} not found in trait {trait_name}")]
MethodNotFound {
trait_name: String,
method_name: String,
},
#[error("Failed to encode Runtime API inputs: {0}")]
CouldNotEncodeInputs(frame_decode::runtime_apis::RuntimeApiInputsEncodeError),
#[error("Failed to decode Runtime API: {0}")]
CouldNotDecodeResponse(frame_decode::runtime_apis::RuntimeApiDecodeError<u32>),
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum CustomValueError {
#[error("The static custom value address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("The custom value '{0}' was not found")]
NotFound(String),
#[error("Failed to decode custom value: {0}")]
CouldNotDecodeCustomValue(frame_decode::custom_values::CustomValueDecodeError<u32>),
}
/// Something went wrong working with a constant.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ConstantError {
#[error("The static constant address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find constant: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error(
"Constant '{constant_name}' not found in pallet {pallet_name} in the live chain metadata"
)]
ConstantNameNotFound {
pallet_name: String,
constant_name: String,
},
#[error("Failed to decode constant: {0}")]
CouldNotDecodeConstant(frame_decode::constants::ConstantDecodeError<u32>),
#[error("Cannot obtain constant information from metadata: {0}")]
ConstantInfoError(frame_decode::constants::ConstantInfoError<'static>),
}
/// Something went wrong trying to encode or decode a storage address.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageError {
#[error("The static storage address used is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find storage value: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error(
"Storage entry '{entry_name}' not found in pallet {pallet_name} in the live chain metadata"
)]
StorageEntryNotFound {
pallet_name: String,
entry_name: String,
},
#[error("Cannot obtain storage information from metadata: {0}")]
StorageInfoError(frame_decode::storage::StorageInfoError<'static>),
#[error("Cannot encode storage key: {0}")]
StorageKeyEncodeError(frame_decode::storage::StorageKeyEncodeError),
#[error("Cannot create a key to iterate over a plain entry")]
CannotIterPlainEntry {
pallet_name: String,
entry_name: String,
},
#[error(
"Wrong number of key parts provided to iterate a storage address. We expected at most {max_expected} key parts but got {got} key parts"
)]
WrongNumberOfKeyPartsProvidedForIterating { max_expected: usize, got: usize },
#[error(
"Wrong number of key parts provided to fetch a storage address. We expected {expected} key parts but got {got} key parts"
)]
WrongNumberOfKeyPartsProvidedForFetching { expected: usize, got: usize },
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageKeyError {
#[error("Can't decode the storage key: {error}")]
StorageKeyDecodeError {
bytes: Vec<u8>,
error: frame_decode::storage::StorageKeyDecodeError<u32>,
},
#[error("Can't decode the values from the storage key: {0}")]
CannotDecodeValuesInKey(frame_decode::storage::StorageKeyValueDecodeError),
#[error(
"Cannot decode storage key: there were leftover bytes, indicating that the decoding failed"
)]
LeftoverBytes { bytes: Vec<u8> },
#[error("Can't decode a single value from the storage key part at index {index}: {error}")]
CannotDecodeValueInKey {
index: usize,
error: scale_decode::Error,
},
}
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum StorageValueError {
#[error("Cannot decode storage value: {0}")]
CannotDecode(frame_decode::storage::StorageValueDecodeError<u32>),
#[error(
"Cannot decode storage value: there were leftover bytes, indicating that the decoding failed"
)]
LeftoverBytes { bytes: Vec<u8> },
}
/// An error that can be encountered when constructing a transaction.
#[derive(Debug, DeriveError)]
#[allow(missing_docs)]
pub enum ExtrinsicError {
#[error("The extrinsic payload is not compatible with the live chain")]
IncompatibleCodegen,
#[error("Can't find extrinsic: pallet with name {0} not found")]
PalletNameNotFound(String),
#[error("Can't find extrinsic: call name {call_name} doesn't exist in pallet {pallet_name}")]
CallNameNotFound {
pallet_name: String,
call_name: String,
},
#[error("Can't encode the extrinsic call data: {0}")]
CannotEncodeCallData(scale_encode::Error),
#[error("Subxt does not support the extrinsic versions expected by the chain")]
UnsupportedVersion,
#[error("Cannot construct the required transaction extensions: {0}")]
Params(#[from] ExtrinsicParamsError),
#[error("Cannot decode transaction extension '{name}': {error}")]
CouldNotDecodeTransactionExtension {
/// The extension name.
name: String,
/// The decode error.
error: scale_decode::Error,
},
#[error(
"After decoding the extrinsic at index {extrinsic_index}, {num_leftover_bytes} bytes were left, suggesting that decoding may have failed"
)]
@@ -62,140 +247,49 @@ pub enum BlockError {
/// Number of bytes leftover after decoding the extrinsic.
num_leftover_bytes: usize,
},
/// Something went wrong decoding the extrinsic.
#[error("Failed to decode extrinsic at index {extrinsic_index}: {error}")]
ExtrinsicDecodeError {
/// Index of the extrinsic that failed to decode.
#[error("{0}")]
ExtrinsicDecodeErrorAt(#[from] ExtrinsicDecodeErrorAt),
#[error("Failed to decode the fields of an extrinsic at index {extrinsic_index}: {error}")]
CannotDecodeFields {
/// Index of the extrinsic whose fields we could not decode
extrinsic_index: usize,
/// The decode error.
error: ExtrinsicDecodeError,
error: scale_decode::Error,
},
#[error("Failed to decode the extrinsic at index {extrinsic_index} to a root enum: {error}")]
CannotDecodeIntoRootExtrinsic {
/// Index of the extrinsic that we failed to decode
extrinsic_index: usize,
/// The decode error.
error: scale_decode::Error,
},
}
/// An alias for [`frame_decode::extrinsics::ExtrinsicDecodeError`].
///
pub type ExtrinsicDecodeError = frame_decode::extrinsics::ExtrinsicDecodeError;
/// Something went wrong trying to access details in the metadata.
#[derive(Clone, Debug, PartialEq, DeriveError)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum MetadataError {
/// The DispatchError type isn't available in the metadata
#[error("The DispatchError type isn't available")]
DispatchErrorNotFound,
/// Type not found in metadata.
#[error("Type with ID {0} not found")]
TypeNotFound(u32),
/// Pallet not found (index).
#[error("Pallet with index {0} not found")]
PalletIndexNotFound(u8),
/// Pallet not found (name).
#[error("Pallet with name {0} not found")]
PalletNameNotFound(String),
/// Variant not found.
#[error("Variant with index {0} not found")]
VariantIndexNotFound(u8),
/// Constant not found.
#[error("Constant with name {0} not found")]
ConstantNameNotFound(String),
/// Call not found.
#[error("Call with name {0} not found")]
CallNameNotFound(String),
/// Runtime trait not found.
#[error("Runtime trait with name {0} not found")]
RuntimeTraitNotFound(String),
/// Runtime method not found.
#[error("Runtime method with name {0} not found")]
RuntimeMethodNotFound(String),
/// View Function not found.
#[error("View Function with query ID {} not found", hex::encode(.0))]
ViewFunctionNotFound([u8; 32]),
/// Call type not found in metadata.
#[error("Call type not found in pallet with index {0}")]
CallTypeNotFoundInPallet(u8),
/// Event type not found in metadata.
#[error("Event type not found in pallet with index {0}")]
EventTypeNotFoundInPallet(u8),
/// Storage details not found in metadata.
#[error("Storage details not found in pallet with name {0}")]
StorageNotFoundInPallet(String),
/// Storage entry not found.
#[error("Storage entry {0} not found")]
StorageEntryNotFound(String),
/// The generated interface used is not compatible with the node.
#[error("The generated code is not compatible with the node")]
IncompatibleCodegen,
/// Custom value not found.
#[error("Custom value with name {0} not found")]
CustomValueNameNotFound(String),
#[allow(missing_docs)]
#[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
pub struct ExtrinsicDecodeErrorAt {
pub extrinsic_index: usize,
pub error: ExtrinsicDecodeErrorAtReason,
}
/// Something went wrong trying to encode or decode a storage address.
#[derive(Clone, Debug, DeriveError)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageAddressError {
/// Storage lookup does not have the expected number of keys.
#[error("Storage lookup requires {expected} keys but more keys have been provided.")]
TooManyKeys {
/// The number of keys provided in the storage address.
expected: usize,
},
/// 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,
},
/// We weren't given enough bytes to decode the storage address/key.
#[error("Not enough remaining bytes to decode the storage address/key")]
NotEnoughBytes,
/// We have leftover bytes after decoding the storage address.
#[error("We have leftover bytes after decoding the storage address")]
TooManyBytes,
/// The bytes of a storage address are not the expected address for decoding the storage keys of the address.
#[error(
"Storage address bytes are not the expected format. Addresses need to be at least 16 bytes (pallet ++ entry) and follow a structure given by the hashers defined in the metadata"
)]
UnexpectedAddressBytes,
/// An invalid hasher was used to reconstruct a value from a chunk of bytes that is part of a storage address. Hashers where the hash does not contain the original value are invalid for this purpose.
#[error(
"An invalid hasher was used to reconstruct a value with type ID {ty_id} from a hash formed by a {hasher:?} hasher. This is only possible for concat-style hashers or the identity hasher"
)]
HasherCannotReconstructKey {
/// Type id of the key's type.
ty_id: u32,
/// The invalid hasher that caused this error.
hasher: StorageHasher,
},
}
/// An error that can be encountered when constructing a transaction.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
pub enum ExtrinsicError {
/// Transaction version not supported by Subxt.
#[error("Subxt does not support the extrinsic versions expected by the chain")]
UnsupportedVersion,
/// Issue encoding transaction extensions.
#[error("Cannot construct the required transaction extensions: {0}")]
Params(#[from] ExtrinsicParamsError),
}
impl From<ExtrinsicParamsError> for Error {
fn from(value: ExtrinsicParamsError) -> Self {
Error::Extrinsic(value.into())
}
#[allow(missing_docs)]
pub enum ExtrinsicDecodeErrorAtReason {
#[error("{0}")]
DecodeError(frame_decode::extrinsics::ExtrinsicDecodeError),
#[error("Leftover bytes")]
LeftoverBytes(Vec<u8>),
}
/// An error that can be emitted when trying to construct an instance of [`crate::config::ExtrinsicParams`],
/// encode data from the instance, or match on signed extensions.
#[derive(Debug, DeriveError)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ExtrinsicParamsError {
/// Cannot find a type id in the metadata. The context provides some additional
/// information about the source of the error (eg the signed extension name).
#[error("Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
@@ -203,10 +297,8 @@ pub enum ExtrinsicParamsError {
/// Some arbitrary context to help narrow the source of the error.
context: &'static str,
},
/// A signed extension in use on some chain was not provided.
#[error("The chain expects a signed extension with the name {0}, but we did not provide one")]
UnknownTransactionExtension(String),
/// Some custom error.
#[error("Error constructing extrinsic parameters: {0}")]
Custom(Box<dyn core::error::Error + Send + Sync + 'static>),
}
+62 -26
View File
@@ -10,7 +10,8 @@
//! use subxt_macro::subxt;
//! use subxt_core::config::PolkadotConfig;
//! use subxt_core::events;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//! use subxt_core::dynamic::Value;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -21,7 +22,7 @@
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../artifacts/polkadot_metadata_full.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Some bytes representing events (located in System.Events storage):
//! let event_bytes = hex::decode("1c00000000000000a2e9b53d5517020000000100000000000310c96d901d0102000000020000000408d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a030000000000000000000000000000020000000402d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48102700000000000000000000000000000000020000000407be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25fbeea5a030000000000000000000000000000020000002100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a03000000000000000000000000000000000000000000000000000000000000020000000000426df03e00000000").unwrap();
@@ -34,10 +35,11 @@
//! let ev = ev.unwrap();
//! println!("Index: {}", ev.index());
//! println!("Name: {}.{}", ev.pallet_name(), ev.variant_name());
//! println!("Fields: {:?}", ev.field_values().unwrap());
//! println!("Fields: {:?}", ev.decode_as_fields::<Value>().unwrap());
//! }
//! ```
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use codec::{Compact, Decode, Encode};
@@ -46,9 +48,9 @@ use scale_decode::{DecodeAsFields, DecodeAsType};
use subxt_metadata::PalletMetadata;
use crate::{
Error, Metadata,
Metadata,
config::{Config, HashFor},
error::MetadataError,
error::EventsError,
};
/// Create a new [`Events`] instance from the given bytes.
@@ -148,7 +150,7 @@ impl<T: Config> Events<T> {
// use of it with our `FilterEvents` stuff.
pub fn iter(
&self,
) -> impl Iterator<Item = Result<EventDetails<T>, Error>> + Send + Sync + 'static {
) -> impl Iterator<Item = Result<EventDetails<T>, EventsError>> + Send + Sync + 'static {
// The event bytes ignoring the compact encoded length on the front:
let event_bytes = self.event_bytes.clone();
let metadata = self.metadata.clone();
@@ -184,25 +186,25 @@ impl<T: Config> Events<T> {
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `Ev` type.
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
pub fn find<Ev: StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
self.iter()
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the first event found which decodes to the provided `Ev` type.
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_first<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().next().transpose()
}
/// Iterate through the events using metadata to dynamically decode and skip
/// them, and return the last event found which decodes to the provided `Ev` type.
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_last<Ev: StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().last().transpose()
}
/// Find an event that decodes to the type provided. Returns true if it was found.
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, Error> {
pub fn has<Ev: StaticEvent>(&self) -> Result<bool, EventsError> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
@@ -246,23 +248,29 @@ impl<T: Config> EventDetails<T> {
all_bytes: Arc<[u8]>,
start_idx: usize,
index: u32,
) -> Result<EventDetails<T>, Error> {
) -> Result<EventDetails<T>, EventsError> {
let input = &mut &all_bytes[start_idx..];
let phase = Phase::decode(input)?;
let phase = Phase::decode(input).map_err(EventsError::CannotDecodePhase)?;
let event_start_idx = all_bytes.len() - input.len();
let pallet_index = u8::decode(input)?;
let variant_index = u8::decode(input)?;
let pallet_index = u8::decode(input).map_err(EventsError::CannotDecodePalletIndex)?;
let variant_index = u8::decode(input).map_err(EventsError::CannotDecodeVariantIndex)?;
let event_fields_start_idx = all_bytes.len() - input.len();
// Get metadata for the event:
let event_pallet = metadata.pallet_by_index_err(pallet_index)?;
let event_pallet = metadata
.pallet_by_index(pallet_index)
.ok_or_else(|| EventsError::CannotFindPalletWithIndex(pallet_index))?;
let event_variant = event_pallet
.event_variant_by_index(variant_index)
.ok_or(MetadataError::VariantIndexNotFound(variant_index))?;
.ok_or_else(|| EventsError::CannotFindVariantWithIndex {
pallet_name: event_pallet.name().to_string(),
variant_index,
})?;
tracing::debug!(
"Decoding Event '{}::{}'",
event_pallet.name(),
@@ -278,14 +286,23 @@ impl<T: Config> EventDetails<T> {
metadata.types(),
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(scale_decode::Error::from)?;
.map_err(|e| EventsError::CannotDecodeFieldInEvent {
pallet_name: event_pallet.name().to_string(),
event_name: event_variant.name.clone(),
field_name: field_metadata
.name
.clone()
.unwrap_or("<unknown>".to_string()),
reason: e,
})?;
}
// the end of the field bytes.
let event_fields_end_idx = all_bytes.len() - input.len();
// topics come after the event data in EventRecord.
let topics = Vec::<HashFor<T>>::decode(input)?;
let topics =
Vec::<HashFor<T>>::decode(input).map_err(EventsError::CannotDecodeEventTopics)?;
// what bytes did we skip over in total, including topics.
let end_idx = all_bytes.len() - input.len();
@@ -367,7 +384,7 @@ impl<T: Config> EventDetails<T> {
/// Decode and provide the event fields back in the form of a [`scale_value::Composite`]
/// type which represents the named or unnamed fields that were present in the event.
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, EventsError> {
let bytes = &mut self.field_bytes();
let event_metadata = self.event_metadata();
@@ -378,14 +395,20 @@ impl<T: Config> EventDetails<T> {
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let decoded =
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
E::decode_as_fields(bytes, &mut fields, self.metadata.types()).map_err(|e| {
EventsError::CannotDecodeEventFields {
pallet_name: event_metadata.pallet.name().to_string(),
event_name: event_metadata.variant.name.clone(),
reason: e,
}
})?;
Ok(decoded)
}
/// Attempt to decode these [`EventDetails`] into a type representing the event fields.
/// Such types are exposed in the codegen as `pallet_name::events::EventName` types.
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error> {
pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, EventsError> {
let ev_metadata = self.event_metadata();
if ev_metadata.pallet.name() == E::PALLET && ev_metadata.variant.name == E::EVENT {
let mut fields = ev_metadata
@@ -394,7 +417,12 @@ impl<T: Config> EventDetails<T> {
.iter()
.map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
let decoded =
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())
.map_err(|e| EventsError::CannotDecodeEventFields {
pallet_name: E::PALLET.to_string(),
event_name: E::EVENT.to_string(),
reason: e,
})?;
Ok(Some(decoded))
} else {
Ok(None)
@@ -404,14 +432,22 @@ impl<T: Config> EventDetails<T> {
/// Attempt to decode these [`EventDetails`] into a root event type (which includes
/// the pallet and event enum variants as well as the event fields). A compatible
/// type for this is exposed via static codegen as a root level `Event` type.
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, Error> {
pub fn as_root_event<E: DecodeAsType>(&self) -> Result<E, EventsError> {
let bytes = &self.all_bytes[self.event_start_idx..self.event_fields_end_idx];
let decoded = E::decode_as_type(
&mut &bytes[..],
self.metadata.outer_enums().event_enum_ty(),
self.metadata.types(),
)?;
)
.map_err(|e| {
let md = self.event_metadata();
EventsError::CannotDecodeEventEnum {
pallet_name: md.pallet.name().to_string(),
event_name: md.variant.name.clone(),
reason: e,
}
})?;
Ok(decoded)
}
@@ -553,7 +589,7 @@ pub(crate) mod test_utils {
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
Metadata::from(metadata)
metadata
}
/// Build an `Events` object for test purposes, based on the details provided,
@@ -623,7 +659,7 @@ mod tests {
expected: TestRawEventDetails,
) {
let actual_fields_no_context: Vec<_> = actual
.field_values()
.decode_as_fields::<scale_value::Composite<()>>()
.expect("can decode field values (2)")
.into_values()
.map(|value| value.remove_context())
+1 -3
View File
@@ -12,7 +12,6 @@
//! - [`blocks`]: decode and explore block bodies.
//! - [`constants`]: access and validate the constant addresses in some metadata.
//! - [`custom_values`]: access and validate the custom value addresses in some metadata.
//! - [`metadata`]: decode bytes into the metadata used throughout this library.
//! - [`storage`]: construct storage request payloads and decode the results you'd get back.
//! - [`tx`]: construct and sign transactions (extrinsics).
//! - [`runtime_api`]: construct runtime API request payloads and decode the results you'd get back.
@@ -31,7 +30,6 @@ pub mod custom_values;
pub mod dynamic;
pub mod error;
pub mod events;
pub mod metadata;
pub mod runtime_api;
pub mod storage;
pub mod tx;
@@ -40,7 +38,7 @@ pub mod view_functions;
pub use config::Config;
pub use error::Error;
pub use metadata::Metadata;
pub use subxt_metadata::Metadata;
/// Re-exports of some of the key external crates.
pub mod ext {
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2019-2024 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 alloc::vec::Vec;
/// This trait is implemented for all types that also implement [`scale_decode::DecodeAsType`].
pub trait DecodeWithMetadata: Sized {
/// 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, scale_decode::Error>;
}
impl<T: scale_decode::DecodeAsType> DecodeWithMetadata for T {
fn decode_with_metadata(
bytes: &mut &[u8],
type_id: u32,
metadata: &Metadata,
) -> Result<T, scale_decode::Error> {
let val = T::decode_as_type(bytes, type_id, metadata.types())?;
Ok(val)
}
}
/// This trait is implemented for all types that also implement [`scale_encode::EncodeAsType`].
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<(), scale_encode::Error>;
}
impl<T: scale_encode::EncodeAsType> EncodeWithMetadata for T {
/// 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<(), scale_encode::Error> {
self.encode_as_type_to(type_id, metadata.types(), bytes)?;
Ok(())
}
}
-81
View File
@@ -1,81 +0,0 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::MetadataError;
use alloc::borrow::ToOwned;
use alloc::sync::Arc;
/// A cheaply clone-able representation of the runtime metadata received from a node.
#[derive(Clone, Debug)]
pub struct Metadata {
inner: Arc<subxt_metadata::Metadata>,
}
impl core::ops::Deref for Metadata {
type Target = subxt_metadata::Metadata;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Metadata {
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
pub fn pallet_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::PalletMetadata<'_>, MetadataError> {
self.pallet_by_name(name)
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
}
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
pub fn pallet_by_index_err(
&self,
index: u8,
) -> Result<subxt_metadata::PalletMetadata<'_>, MetadataError> {
self.pallet_by_index(index)
.ok_or(MetadataError::PalletIndexNotFound(index))
}
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
pub fn runtime_api_trait_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::RuntimeApiMetadata<'_>, MetadataError> {
self.runtime_api_trait_by_name(name)
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
}
/// Identical to `metadata.custom().get(name)`, but returns an error if the trait is not found.
pub fn custom_value_by_name_err(
&self,
name: &str,
) -> Result<subxt_metadata::CustomValueMetadata<'_>, MetadataError> {
self.custom()
.get(name)
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
}
}
impl From<subxt_metadata::Metadata> for Metadata {
fn from(md: subxt_metadata::Metadata) -> Self {
Metadata {
inner: Arc::new(md),
}
}
}
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
type Error = subxt_metadata::TryFromError;
fn try_from(value: frame_metadata::RuntimeMetadataPrefixed) -> Result<Self, Self::Error> {
subxt_metadata::Metadata::try_from(value).map(Metadata::from)
}
}
impl codec::Decode for Metadata {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
subxt_metadata::Metadata::decode(input).map(Metadata::from)
}
}
-34
View File
@@ -1,34 +0,0 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! A [`Metadata`] type, which is used through this crate.
//!
//! This can be decoded from the bytes handed back from a node when asking for metadata.
//!
//! # Examples
//!
//! ```rust
//! use subxt_core::metadata;
//!
//! // We need to fetch the bytes from somewhere, and then we can decode them:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! ```
mod decode_encode_traits;
mod metadata_type;
use codec::Decode;
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
pub use metadata_type::Metadata;
/// Attempt to decode some bytes into [`Metadata`], returning an error
/// if decoding fails.
///
/// This is a shortcut for importing [`codec::Decode`] and using the
/// implementation of that on [`Metadata`].
pub fn decode_from(bytes: &[u8]) -> Result<Metadata, codec::Error> {
Metadata::decode(&mut &*bytes)
}
+48 -31
View File
@@ -10,7 +10,7 @@
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::runtime_api;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -21,7 +21,7 @@
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Build a storage query to access account information.
//! let payload = polkadot::apis().metadata().metadata_versions();
@@ -43,61 +43,78 @@
pub mod payload;
use crate::error::{Error, MetadataError};
use crate::metadata::{DecodeWithMetadata, Metadata};
use alloc::borrow::ToOwned;
use crate::Metadata;
use crate::error::RuntimeApiError;
use alloc::format;
use alloc::string::String;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use payload::Payload;
use scale_decode::IntoVisitor;
/// Run the validation logic against some runtime API payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the runtime API in question do not exist at all)
pub fn validate<P: Payload>(payload: &P, metadata: &Metadata) -> Result<(), Error> {
let Some(static_hash) = payload.validation_hash() else {
pub fn validate<P: Payload>(payload: P, metadata: &Metadata) -> Result<(), RuntimeApiError> {
let Some(hash) = payload.validation_hash() else {
return Ok(());
};
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
let Some(api_method) = api_trait.method_by_name(payload.method_name()) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
let trait_name = payload.trait_name();
let method_name = payload.method_name();
let runtime_hash = api_method.hash();
if static_hash != runtime_hash {
return Err(MetadataError::IncompatibleCodegen.into());
let api_trait = metadata
.runtime_api_trait_by_name(trait_name)
.ok_or_else(|| RuntimeApiError::TraitNotFound(trait_name.to_string()))?;
let api_method =
api_trait
.method_by_name(method_name)
.ok_or_else(|| RuntimeApiError::MethodNotFound {
trait_name: trait_name.to_string(),
method_name: method_name.to_string(),
})?;
if hash != api_method.hash() {
Err(RuntimeApiError::IncompatibleCodegen)
} else {
Ok(())
}
Ok(())
}
/// Return the name of the runtime API call from the payload.
pub fn call_name<P: Payload>(payload: &P) -> String {
pub fn call_name<P: Payload>(payload: P) -> String {
format!("{}_{}", payload.trait_name(), payload.method_name())
}
/// Return the encoded call args given a runtime API payload.
pub fn call_args<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, Error> {
payload.encode_args(metadata)
pub fn call_args<P: Payload>(payload: P, metadata: &Metadata) -> Result<Vec<u8>, RuntimeApiError> {
let value = frame_decode::runtime_apis::encode_runtime_api_inputs(
payload.trait_name(),
payload.method_name(),
payload.args(),
metadata,
metadata.types(),
)
.map_err(RuntimeApiError::CouldNotEncodeInputs)?;
Ok(value)
}
/// Decode the value bytes at the location given by the provided runtime API payload.
pub fn decode_value<P: Payload>(
bytes: &mut &[u8],
payload: &P,
payload: P,
metadata: &Metadata,
) -> Result<P::ReturnType, Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(payload.trait_name())?
.method_by_name(payload.method_name())
.ok_or_else(|| MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned()))?;
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
api_method.output_ty(),
) -> Result<P::ReturnType, RuntimeApiError> {
let value = frame_decode::runtime_apis::decode_runtime_api_response(
payload.trait_name(),
payload.method_name(),
bytes,
metadata,
)?;
metadata.types(),
P::ReturnType::into_visitor(),
)
.map_err(RuntimeApiError::CouldNotDecodeResponse)?;
Ok(val)
Ok(value)
}
+58 -86
View File
@@ -6,45 +6,19 @@
//! runtime API calls that can be made.
use alloc::borrow::Cow;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use frame_decode::runtime_apis::IntoEncodableValues;
use scale_decode::DecodeAsType;
use crate::Error;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::metadata::{DecodeWithMetadata, Metadata};
/// This represents a runtime API payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - runtime function name
///
/// The function name of the runtime API call. This is obtained by concatenating
/// the runtime trait name with the trait's method.
///
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
/// contains the `metadata_at_version` function. The corresponding runtime function
/// is `Metadata_metadata_at_version`.
///
/// - encoded arguments
///
/// Each argument of the runtime function must be scale-encoded.
/// This represents a runtime API payload that can be used to call a Runtime API on
/// a chain and decode the response.
pub trait Payload {
/// Type of the arguments.
type ArgsType: IntoEncodableValues;
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
type ReturnType: DecodeAsType;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
@@ -52,16 +26,8 @@ pub trait Payload {
/// The runtime API method name.
fn method_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`Payload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// The input arguments.
fn args(&self) -> &Self::ArgsType;
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -69,29 +35,50 @@ pub trait Payload {
}
}
// Any reference to a payload is a valid payload.
impl<P: Payload + ?Sized> Payload for &'_ P {
type ArgsType = P::ArgsType;
type ReturnType = P::ReturnType;
fn trait_name(&self) -> &str {
P::trait_name(*self)
}
fn method_name(&self) -> &str {
P::method_name(*self)
}
fn args(&self) -> &Self::ArgsType {
P::args(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
P::validation_hash(*self)
}
}
/// A runtime API payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct DefaultPayload<ArgsData, ReturnTy> {
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
pub struct StaticPayload<ArgsType, ReturnType> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args_data: ArgsData,
args: ArgsType,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
_marker: PhantomData<ReturnType>,
}
/// A statically generated runtime API payload.
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
/// A dynamic runtime API payload.
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
for DefaultPayload<ArgsData, ReturnTy>
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
type ReturnType = ReturnTy;
type ArgsType = ArgsType;
type ReturnType = ReturnType;
fn trait_name(&self) -> &str {
&self.trait_name
@@ -101,18 +88,8 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
&self.method_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let api_method = metadata
.runtime_api_trait_by_name_err(&self.trait_name)?
.method_by_name(&self.method_name)
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
let mut fields = api_method
.inputs()
.map(|input| scale_encode::Field::named(input.ty, &input.name));
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
fn args(&self) -> &Self::ArgsType {
&self.args
}
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -120,23 +97,23 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
}
}
impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
/// Create a new [`DefaultPayload`].
impl<ArgsType, ReturnTy> StaticPayload<ArgsType, ReturnTy> {
/// Create a new [`StaticPayload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsData,
args: ArgsType,
) -> Self {
DefaultPayload {
trait_name: Cow::Owned(trait_name.into()),
method_name: Cow::Owned(method_name.into()),
args_data,
StaticPayload {
trait_name: trait_name.into().into(),
method_name: method_name.into().into(),
args,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`DefaultPayload`] using static function name
/// Create a new static [`StaticPayload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
@@ -144,13 +121,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args_data: ArgsData,
args: ArgsType,
hash: [u8; 32],
) -> DefaultPayload<ArgsData, ReturnTy> {
DefaultPayload {
) -> StaticPayload<ArgsType, ReturnTy> {
StaticPayload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args_data,
args,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
@@ -173,18 +150,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
pub fn method_name(&self) -> &str {
&self.method_name
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicPayload`].
pub fn dynamic(
pub fn dynamic<ArgsType, ReturnType>(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: impl Into<Composite<()>>,
) -> DynamicPayload {
DefaultPayload::new(trait_name, method_name, args_data.into())
args_data: ArgsType,
) -> DynamicPayload<ArgsType, ReturnType> {
DynamicPayload::new(trait_name, method_name, args_data)
}
+105 -114
View File
@@ -4,143 +4,125 @@
//! Construct addresses to access storage entries with.
use crate::{
dynamic::DecodedValueThunk,
error::{Error, MetadataError},
metadata::{DecodeWithMetadata, Metadata},
utils::Yes,
};
use derive_where::derive_where;
use alloc::borrow::{Cow, ToOwned};
use crate::utils::{Maybe, YesMaybe};
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use frame_decode::storage::{IntoDecodableValues, IntoEncodableValues};
use scale_decode::DecodeAsType;
// Re-export types used here:
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageHashersIter, StorageKey};
/// This represents a storage address. Anything implementing this trait
/// can be used to fetch and iterate over storage entries.
/// A storage address. This allows access to a given storage entry, which can then
/// be iterated over or fetched from by providing the relevant set of keys, or
/// otherwise inspected.
pub trait Address {
/// The target type of the value that lives at this address.
type Target: DecodeWithMetadata;
/// The keys type used to construct this address.
type Keys: StorageKey;
/// 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;
/// All of the keys required to get to an individual value at this address.
/// Keys must always impl [`IntoEncodableValues`], and for iteration must
/// also impl [`frame_decode::storage::IntoDecodableValues`].
type KeyParts: IntoEncodableValues + IntoDecodableValues;
/// Type of the storage value at this location.
type Value: DecodeAsType;
/// Does the address point to a plain value (as opposed to a map)?
/// Set to [`crate::utils::Yes`] to enable APIs which require a map,
/// or [`crate::utils::Maybe`] to enable APIs which allow a map.
type IsPlain: YesMaybe;
/// The name of the pallet that the entry lives under.
/// The pallet containing this storage entry.
fn pallet_name(&self) -> &str;
/// The name of the entry in a given pallet that the item is at.
/// The name of the storage entry.
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>;
/// Return a unique hash for this address which can be used to validate it against metadata.
fn validation_hash(&self) -> Option<[u8; 32]>;
}
// Any reference to an address is a valid address.
impl<A: Address + ?Sized> Address for &'_ A {
type KeyParts = A::KeyParts;
type Value = A::Value;
type IsPlain = A::IsPlain;
fn pallet_name(&self) -> &str {
A::pallet_name(*self)
}
fn entry_name(&self) -> &str {
A::entry_name(*self)
}
/// 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
A::validation_hash(*self)
}
}
/// A concrete storage address. This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; Keys)]
pub struct DefaultAddress<Keys: StorageKey, ReturnTy, Fetchable, Defaultable, Iterable> {
/// An address which is generated by the static APIs.
pub struct StaticAddress<KeyParts, Value, IsPlain> {
pallet_name: Cow<'static, str>,
entry_name: Cow<'static, str>,
keys: Keys,
validation_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<(ReturnTy, Fetchable, Defaultable, Iterable)>,
marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>,
}
/// A storage address constructed by the static codegen.
pub type StaticAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable> =
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>;
/// A typical storage address constructed at runtime rather than via the `subxt` macro; this
/// has no restriction on what it can be used for (since we don't statically know).
pub type DynamicAddress<Keys> = DefaultAddress<Keys, DecodedValueThunk, Yes, Yes, Yes>;
impl<Keys: StorageKey> DynamicAddress<Keys> {
/// Creates a new dynamic address. As `Keys` you can use a `Vec<scale_value::Value>`
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>, keys: Keys) -> Self {
impl<KeyParts, Value, IsPlain> Clone for StaticAddress<KeyParts, Value, IsPlain> {
fn clone(&self) -> Self {
Self {
pallet_name: Cow::Owned(pallet_name.into()),
entry_name: Cow::Owned(entry_name.into()),
keys,
validation_hash: None,
_marker: core::marker::PhantomData,
pallet_name: self.pallet_name.clone(),
entry_name: self.entry_name.clone(),
validation_hash: self.validation_hash,
marker: self.marker,
}
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
/// Create a new [`Address`] using static strings for the pallet and call name.
impl<KeyParts, Value, IsPlain> core::fmt::Debug for StaticAddress<KeyParts, Value, IsPlain> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("StaticAddress")
.field("pallet_name", &self.pallet_name)
.field("entry_name", &self.entry_name)
.field("validation_hash", &self.validation_hash)
.finish()
}
}
impl<KeyParts, Value, IsPlain> StaticAddress<KeyParts, Value, IsPlain> {
/// Create a new [`StaticAddress`] using static strings for the pallet and call name.
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
entry_name: &'static str,
keys: Keys,
hash: [u8; 32],
) -> Self {
pub fn new_static(pallet_name: &'static str, entry_name: &'static str, hash: [u8; 32]) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
keys,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
marker: core::marker::PhantomData,
}
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
{
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(self) -> Self {
/// Create a new address.
pub fn new(pallet_name: impl Into<String>, entry_name: impl Into<String>) -> Self {
Self {
pallet_name: pallet_name.into().into(),
entry_name: entry_name.into().into(),
validation_hash: None,
..self
marker: core::marker::PhantomData,
}
}
/// Return bytes representing the root of this storage entry (a hash of the pallet and entry name).
pub fn to_root_bytes(&self) -> Vec<u8> {
super::get_address_root_bytes(self)
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
self
}
}
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> Address
for DefaultAddress<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
impl<KeyParts, Value, IsPlain> Address for StaticAddress<KeyParts, Value, IsPlain>
where
Keys: StorageKey,
ReturnTy: DecodeWithMetadata,
KeyParts: IntoEncodableValues + IntoDecodableValues,
Value: DecodeAsType,
IsPlain: YesMaybe,
{
type Target = ReturnTy;
type Keys = Keys;
type IsFetchable = Fetchable;
type IsDefaultable = Defaultable;
type IsIterable = Iterable;
type KeyParts = KeyParts;
type Value = Value;
type IsPlain = IsPlain;
fn pallet_name(&self) -> &str {
&self.pallet_name
@@ -150,31 +132,40 @@ where
&self.entry_name
}
fn append_entry_bytes(&self, metadata: &Metadata, bytes: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(self.pallet_name())?;
let storage = pallet
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(self.pallet_name().to_owned()))?;
let entry = storage
.entry_by_name(self.entry_name())
.ok_or_else(|| MetadataError::StorageEntryNotFound(self.entry_name().to_owned()))?;
let hashers = StorageHashers::new(entry.entry_type(), metadata.types())?;
self.keys
.encode_storage_key(bytes, &mut hashers.iter(), metadata.types())?;
Ok(())
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
/// Construct a new dynamic storage lookup.
pub fn dynamic<Keys: StorageKey>(
impl<A: AsRef<str>, B: AsRef<str>> Address for (A, B) {
type KeyParts = Vec<scale_value::Value>;
type Value = scale_value::Value;
type IsPlain = Maybe;
fn pallet_name(&self) -> &str {
self.0.as_ref()
}
fn entry_name(&self) -> &str {
self.1.as_ref()
}
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
/// A dynamic address is simply a [`StaticAddress`] which asserts that the
/// entry *might* be a map and *might* have a default value.
pub type DynamicAddress<KeyParts = Vec<scale_value::Value>, Value = scale_value::Value> =
StaticAddress<KeyParts, Value, Maybe>;
/// Construct a new dynamic storage address. You can define the type of the
/// storage keys and value yourself here, but have no guarantee that they will
/// be correct.
pub fn dynamic<KeyParts: IntoEncodableValues, Value: DecodeAsType>(
pallet_name: impl Into<String>,
entry_name: impl Into<String>,
storage_entry_keys: Keys,
) -> DynamicAddress<Keys> {
DynamicAddress::new(pallet_name, entry_name, storage_entry_keys)
) -> DynamicAddress<KeyParts, Value> {
DynamicAddress::<KeyParts, Value>::new(pallet_name.into(), entry_name.into())
}
+39 -87
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
@@ -10,7 +10,7 @@
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::storage;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -21,45 +21,54 @@
//!
//! // Some metadata we'll use to work with storage entries:
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
//! let metadata = Metadata::decode_from(&metadata_bytes[..]).unwrap();
//!
//! // Build a storage query to access account information.
//! let account = dev::alice().public_key().into();
//! let address = polkadot::storage().system().account(account);
//! let address = polkadot::storage().system().account();
//!
//! // We can validate that the address is compatible with the given metadata.
//! storage::validate(&address, &metadata).unwrap();
//!
//! // Encode the address to bytes. These can be sent to a node to query the value.
//! storage::get_address_bytes(&address, &metadata).unwrap();
//! // We can fetch details about the storage entry associated with this address:
//! let entry = storage::entry(address, &metadata).unwrap();
//!
//! // If we were to obtain a value back from the node at that address, we could
//! // then decode it using the same address and metadata like so:
//! // .. including generating a key to fetch the entry with:
//! let fetch_key = entry.fetch_key((dev::alice().public_key().into(),)).unwrap();
//!
//! // .. or generating a key to iterate over entries with at a given depth:
//! let iter_key = entry.iter_key(()).unwrap();
//!
//! // Given a value, we can decode it:
//! let value_bytes = hex::decode("00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080").unwrap();
//! let value = storage::decode_value(&mut &*value_bytes, &address, &metadata).unwrap();
//! let value = entry.value(value_bytes).decode().unwrap();
//!
//! println!("Alice's account info: {value:?}");
//! ```
mod prefix_of;
mod storage_entry;
mod storage_key;
mod utils;
mod storage_key_value;
mod storage_value;
pub mod address;
use crate::{Error, Metadata, error::MetadataError, metadata::DecodeWithMetadata};
use crate::{Metadata, error::StorageError};
use address::Address;
use alloc::vec::Vec;
use alloc::string::ToString;
// This isn't a part of the public API, but expose here because it's useful in Subxt.
#[doc(hidden)]
pub use utils::lookup_storage_entry_details;
pub use prefix_of::{EqualOrPrefixOf, PrefixOf};
pub use storage_entry::{StorageEntry, entry};
pub use storage_key::{StorageHasher, StorageKey, StorageKeyPart};
pub use storage_key_value::StorageKeyValue;
pub use storage_value::StorageValue;
/// When the provided `address` is statically generated via the `#[subxt]` macro, this validates
/// that the shape of the storage value is the same as the shape expected by the static address.
///
/// When the provided `address` is dynamic (and thus does not come with any expectation of the
/// shape of the constant value), this just returns `Ok(())`
pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<(), Error> {
pub fn validate<Addr: Address>(address: Addr, metadata: &Metadata) -> Result<(), StorageError> {
let Some(hash) = address.validation_hash() else {
return Ok(());
};
@@ -67,76 +76,19 @@ pub fn validate<Addr: Address>(address: &Addr, metadata: &Metadata) -> Result<()
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let pallet_metadata = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| StorageError::PalletNameNotFound(pallet_name.to_string()))?;
let storage_hash = pallet_metadata.storage_hash(entry_name).ok_or_else(|| {
StorageError::StorageEntryNotFound {
pallet_name: pallet_name.to_string(),
entry_name: entry_name.to_string(),
}
})?;
let Some(expected_hash) = pallet_metadata.storage_hash(entry_name) else {
return Err(MetadataError::IncompatibleCodegen.into());
};
if expected_hash != hash {
return Err(MetadataError::IncompatibleCodegen.into());
if storage_hash != hash {
Err(StorageError::IncompatibleCodegen)
} else {
Ok(())
}
Ok(())
}
/// Given a storage address and some metadata, this encodes the address into bytes which can be
/// handed to a node to retrieve the corresponding value.
pub fn get_address_bytes<Addr: Address>(
address: &Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
utils::write_storage_address_root_bytes(address, &mut bytes);
address.append_entry_bytes(metadata, &mut bytes)?;
Ok(bytes)
}
/// Given a storage address and some metadata, this encodes the root of the address (ie the pallet
/// and storage entry part) into bytes. If the entry being addressed is inside a map, this returns
/// the bytes needed to iterate over all of the entries within it.
pub fn get_address_root_bytes<Addr: Address>(address: &Addr) -> Vec<u8> {
let mut bytes = Vec::new();
utils::write_storage_address_root_bytes(address, &mut bytes);
bytes
}
/// Given some storage value that we've retrieved from a node, the address used to retrieve it, and
/// metadata from the node, this function attempts to decode the bytes into the target value specified
/// by the address.
pub fn decode_value<Addr: Address>(
bytes: &mut &[u8],
address: &Addr,
metadata: &Metadata,
) -> Result<Addr::Target, Error> {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let (_, entry_metadata) =
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
let value_ty_id = match entry_metadata.entry_type() {
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
};
let val = Addr::Target::decode_with_metadata(bytes, value_ty_id, metadata)?;
Ok(val)
}
/// Return the default value at a given storage address if one is available, or an error otherwise.
pub fn default_value<Addr: Address>(
address: &Addr,
metadata: &Metadata,
) -> Result<Addr::Target, Error> {
let pallet_name = address.pallet_name();
let entry_name = address.entry_name();
let (_, entry_metadata) =
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
let value_ty_id = match entry_metadata.entry_type() {
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
};
let default_bytes = entry_metadata.default_bytes();
let val = Addr::Target::decode_with_metadata(&mut &*default_bytes, value_ty_id, metadata)?;
Ok(val)
}
+195
View File
@@ -0,0 +1,195 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use alloc::vec::Vec;
use frame_decode::helpers::IntoEncodableValues;
use scale_encode::EncodeAsType;
/// For a given set of values that can be used as keys for a storage entry,
/// this is implemented for any prefixes of that set. ie if the keys `(A,B,C)`
/// would access a storage value, then `PrefixOf<(A,B,C)>` is implemented for
/// `(A,B)`, `(A,)` and `()`.
pub trait PrefixOf<Keys>: IntoEncodableValues {}
// If T impls PrefixOf<K>, &T impls PrefixOf<K>.
impl<K, T: PrefixOf<K>> PrefixOf<K> for &T {}
// Impls for tuples up to length 6 (storage maps rarely require more than 2 entries
// so it's very unlikely we'll ever need to go this deep).
impl<A> PrefixOf<(A,)> for () {}
impl<A, B> PrefixOf<(A, B)> for () {}
impl<A, B> PrefixOf<(A, B)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C> PrefixOf<(A, B, C)> for () {}
impl<A, B, C> PrefixOf<(A, B, C)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C> PrefixOf<(A, B, C)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for () {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D> PrefixOf<(A, B, C, D)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for () {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C) where (A, B, C): IntoEncodableValues {}
impl<A, B, C, D, E> PrefixOf<(A, B, C, D, E)> for (A, B, C, D) where
(A, B, C, D): IntoEncodableValues
{
}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for () {}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A,) where (A,): IntoEncodableValues {}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B) where (A, B): IntoEncodableValues {}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C) where
(A, B, C): IntoEncodableValues
{
}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D) where
(A, B, C, D): IntoEncodableValues
{
}
impl<A, B, C, D, E, F> PrefixOf<(A, B, C, D, E, F)> for (A, B, C, D, E) where
(A, B, C, D, E): IntoEncodableValues
{
}
// Vecs are prefixes of vecs. The length is not statically known and so
// these would be given dynamically only, leaving the correct length to the user.
impl<T: EncodeAsType> PrefixOf<Vec<T>> for Vec<T> {}
// We don't use arrays in Subxt for storage entry access, but `IntoEncodableValues`
// supports them so let's allow impls which do use them to benefit too.
macro_rules! array_impl {
($n:literal: $($p:literal)+) => {
$(
impl <T: EncodeAsType> PrefixOf<[T; $n]> for [T; $p] {}
)+
}
}
array_impl!(1: 0);
array_impl!(2: 1 0);
array_impl!(3: 2 1 0);
array_impl!(4: 3 2 1 0);
array_impl!(5: 4 3 2 1 0);
array_impl!(6: 5 4 3 2 1 0);
/// This is much like [`PrefixOf`] except that it also includes `Self` as an allowed type,
/// where `Self` must impl [`IntoEncodableValues`] just as every [`PrefixOf<Self>`] does.
pub trait EqualOrPrefixOf<K>: IntoEncodableValues {}
// Tuples
macro_rules! tuple_impl_eq {
($($t:ident)+) => {
// Any T that is a PrefixOf<Keys> impls EqualOrPrefixOf<keys> too
impl <$($t,)+ T: PrefixOf<($($t,)+)>> EqualOrPrefixOf<($($t,)+)> for T {}
// Keys impls EqualOrPrefixOf<Keys>
impl <$($t),+> EqualOrPrefixOf<($($t,)+)> for ($($t,)+) where ($($t,)+): IntoEncodableValues {}
// &'a Keys impls EqualOrPrefixOf<Keys>
impl <'a, $($t),+> EqualOrPrefixOf<($($t,)+)> for &'a ($($t,)+) where ($($t,)+): IntoEncodableValues {}
}
}
tuple_impl_eq!(A);
tuple_impl_eq!(A B);
tuple_impl_eq!(A B C);
tuple_impl_eq!(A B C D);
tuple_impl_eq!(A B C D E);
tuple_impl_eq!(A B C D E F);
// Vec
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for Vec<T> {}
impl<T: EncodeAsType> EqualOrPrefixOf<Vec<T>> for &Vec<T> {}
// Arrays
macro_rules! array_impl_eq {
($($n:literal)+) => {
$(
impl <A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for [A; $n] {}
impl <'a, A: EncodeAsType> EqualOrPrefixOf<[A; $n]> for &'a [A; $n] {}
)+
}
}
impl<const N: usize, A, T> EqualOrPrefixOf<[A; N]> for T where T: PrefixOf<[A; N]> {}
array_impl_eq!(1 2 3 4 5 6);
#[cfg(test)]
mod test {
use super::*;
struct Test<Keys: IntoEncodableValues>(core::marker::PhantomData<Keys>);
impl<Keys: IntoEncodableValues> Test<Keys> {
fn new() -> Self {
Test(core::marker::PhantomData)
}
fn accepts_prefix_of<P: PrefixOf<Keys>>(&self, keys: P) {
let _encoder = keys.into_encodable_values();
}
fn accepts_eq_or_prefix_of<P: EqualOrPrefixOf<Keys>>(&self, keys: P) {
let _encoder = keys.into_encodable_values();
}
}
#[test]
fn test_prefix_of() {
// In real life we'd have a struct a bit like this:
let t = Test::<(bool, String, u64)>::new();
// And we'd want to be able to call some method like this:
//// This shouldn't work:
// t.accepts_prefix_of((true, String::from("hi"), 0));
t.accepts_prefix_of(&(true, String::from("hi")));
t.accepts_prefix_of((true, String::from("hi")));
t.accepts_prefix_of((true,));
t.accepts_prefix_of(());
let t = Test::<[u64; 5]>::new();
//// This shouldn't work:
// t.accepts_prefix_of([0,1,2,3,4]);
t.accepts_prefix_of([0, 1, 2, 3]);
t.accepts_prefix_of([0, 1, 2, 3]);
t.accepts_prefix_of([0, 1, 2]);
t.accepts_prefix_of([0, 1]);
t.accepts_prefix_of([0]);
t.accepts_prefix_of([]);
}
#[test]
fn test_eq_or_prefix_of() {
// In real life we'd have a struct a bit like this:
let t = Test::<(bool, String, u64)>::new();
// And we'd want to be able to call some method like this:
t.accepts_eq_or_prefix_of(&(true, String::from("hi"), 0));
t.accepts_eq_or_prefix_of(&(true, String::from("hi")));
t.accepts_eq_or_prefix_of((true,));
t.accepts_eq_or_prefix_of(());
t.accepts_eq_or_prefix_of((true, String::from("hi"), 0));
t.accepts_eq_or_prefix_of((true, String::from("hi")));
t.accepts_eq_or_prefix_of((true,));
t.accepts_eq_or_prefix_of(());
let t = Test::<[u64; 5]>::new();
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
t.accepts_eq_or_prefix_of([0, 1, 2]);
t.accepts_eq_or_prefix_of([0, 1]);
t.accepts_eq_or_prefix_of([0]);
t.accepts_eq_or_prefix_of([]);
t.accepts_eq_or_prefix_of([0, 1, 2, 3, 4]);
t.accepts_eq_or_prefix_of([0, 1, 2, 3]);
t.accepts_eq_or_prefix_of([0, 1, 2]);
t.accepts_eq_or_prefix_of([0, 1]);
t.accepts_eq_or_prefix_of([0]);
t.accepts_eq_or_prefix_of([]);
}
}
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{PrefixOf, StorageKeyValue, StorageValue, address::Address};
use crate::error::StorageError;
use crate::utils::YesMaybe;
use alloc::sync::Arc;
use alloc::vec::Vec;
use frame_decode::storage::{IntoEncodableValues, StorageInfo};
use scale_info::PortableRegistry;
use subxt_metadata::Metadata;
/// Create a [`StorageEntry`] to work with a given storage entry.
pub fn entry<'info, Addr: Address>(
address: Addr,
metadata: &'info Metadata,
) -> Result<StorageEntry<'info, Addr>, StorageError> {
super::validate(&address, metadata)?;
use frame_decode::storage::StorageTypeInfo;
let types = metadata.types();
let info = metadata
.storage_info(address.pallet_name(), address.entry_name())
.map_err(|e| StorageError::StorageInfoError(e.into_owned()))?;
Ok(StorageEntry(Arc::new(StorageEntryInner {
address,
info: Arc::new(info),
types,
})))
}
/// This represents a single storage entry (be it a plain value or map).
pub struct StorageEntry<'info, Addr>(Arc<StorageEntryInner<'info, Addr>>);
impl<'info, Addr> Clone for StorageEntry<'info, Addr> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
struct StorageEntryInner<'info, Addr> {
address: Addr,
info: Arc<StorageInfo<'info, u32>>,
types: &'info PortableRegistry,
}
impl<'info, Addr: Address> StorageEntry<'info, Addr> {
/// Name of the pallet containing this storage entry.
pub fn pallet_name(&self) -> &str {
self.0.address.pallet_name()
}
/// Name of the storage entry.
pub fn entry_name(&self) -> &str {
self.0.address.entry_name()
}
/// Is the storage entry a plain value?
pub fn is_plain(&self) -> bool {
self.0.info.keys.is_empty()
}
/// Is the storage entry a map?
pub fn is_map(&self) -> bool {
!self.is_plain()
}
/// Instantiate a [`StorageKeyValue`] for this entry.
///
/// It is expected that the bytes are obtained by iterating key/value pairs at this address.
pub fn key_value(
&self,
key_bytes: impl Into<Arc<[u8]>>,
value_bytes: Vec<u8>,
) -> StorageKeyValue<'info, Addr> {
StorageKeyValue::new(
self.0.info.clone(),
self.0.types,
key_bytes.into(),
value_bytes,
)
}
/// Instantiate a [`StorageValue`] for this entry.
///
/// It is expected that the bytes are obtained by fetching a value at this address.
pub fn value(&self, bytes: Vec<u8>) -> StorageValue<'info, Addr::Value> {
StorageValue::new(self.0.info.clone(), self.0.types, bytes)
}
/// Return the default [`StorageValue`] for this storage entry, if there is one.
pub fn default_value(&self) -> Option<StorageValue<'info, Addr::Value>> {
self.0.info.default_value.as_deref().map(|default_bytes| {
StorageValue::new(self.0.info.clone(), self.0.types, default_bytes.to_vec())
})
}
/// The keys for plain storage values are always 32 byte hashes.
pub fn key_prefix(&self) -> [u8; 32] {
frame_decode::storage::encode_storage_key_prefix(
self.0.address.pallet_name(),
self.0.address.entry_name(),
)
}
// This has a less "strict" type signature and so is just used under the hood.
fn key<Keys: IntoEncodableValues>(&self, key_parts: Keys) -> Result<Vec<u8>, StorageError> {
let key = frame_decode::storage::encode_storage_key_with_info(
self.0.address.pallet_name(),
self.0.address.entry_name(),
key_parts,
&self.0.info,
self.0.types,
)
.map_err(StorageError::StorageKeyEncodeError)?;
Ok(key)
}
/// This constructs a key suitable for fetching a value at the given map storage address. This will error
/// if we can see that the wrong number of key parts are provided.
pub fn fetch_key(&self, key_parts: Addr::KeyParts) -> Result<Vec<u8>, StorageError> {
if key_parts.num_encodable_values() != self.0.info.keys.len() {
Err(StorageError::WrongNumberOfKeyPartsProvidedForFetching {
expected: self.0.info.keys.len(),
got: key_parts.num_encodable_values(),
})
} else {
self.key(key_parts)
}
}
/// This constructs a key suitable for iterating at the given storage address. This will error
/// if we can see that too many key parts are provided.
pub fn iter_key<Keys: PrefixOf<Addr::KeyParts>>(
&self,
key_parts: Keys,
) -> Result<Vec<u8>, StorageError> {
if Addr::IsPlain::is_yes() {
Err(StorageError::CannotIterPlainEntry {
pallet_name: self.0.address.pallet_name().into(),
entry_name: self.0.address.entry_name().into(),
})
} else if key_parts.num_encodable_values() >= self.0.info.keys.len() {
Err(StorageError::WrongNumberOfKeyPartsProvidedForIterating {
max_expected: self.0.info.keys.len() - 1,
got: key_parts.num_encodable_values(),
})
} else {
self.key(key_parts)
}
}
}
+112 -445
View File
@@ -1,471 +1,138 @@
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::utils::hash_bytes;
use crate::error::{Error, MetadataError, StorageAddressError};
use alloc::vec;
use alloc::vec::Vec;
use scale_decode::{DecodeAsType, visitor::IgnoreVisitor};
use scale_encode::EncodeAsType;
use scale_info::{PortableRegistry, TypeDef};
use scale_value::Value;
use subxt_metadata::{StorageEntryType, StorageHasher};
use crate::error::StorageKeyError;
use alloc::sync::Arc;
use core::marker::PhantomData;
use frame_decode::storage::{IntoDecodableValues, StorageInfo, StorageKey as StorageKeyPartInfo};
use scale_info::PortableRegistry;
/// A collection of storage hashers paired with the type ids of the types they should hash.
/// Can be created for each storage entry in the metadata via [`StorageHashers::new()`].
#[derive(Debug)]
pub struct StorageHashers {
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
pub use frame_decode::storage::StorageHasher;
/// This represents the different parts of a storage key.
pub struct StorageKey<'info, KeyParts> {
info: Arc<StorageKeyPartInfo<u32>>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
marker: PhantomData<KeyParts>,
}
impl StorageHashers {
/// Creates new [`StorageHashers`] from a storage entry. Looks at the [`StorageEntryType`] and
/// assigns a hasher to each type id that makes up the key.
pub fn new(storage_entry: &StorageEntryType, types: &PortableRegistry) -> Result<Self, Error> {
let mut hashers_and_ty_ids = vec![];
if let StorageEntryType::Map {
hashers, key_ty, ..
} = storage_entry
{
let ty = types
.resolve(*key_ty)
.ok_or(MetadataError::TypeNotFound(*key_ty))?;
impl<'info, KeyParts: IntoDecodableValues> StorageKey<'info, KeyParts> {
pub(crate) fn new(
info: &StorageInfo<'info, u32>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
) -> Result<Self, StorageKeyError> {
let cursor = &mut &*bytes;
let storage_key_info = frame_decode::storage::decode_storage_key_with_info(
cursor, info, types,
)
.map_err(|e| StorageKeyError::StorageKeyDecodeError {
bytes: bytes.to_vec(),
error: e,
})?;
if hashers.len() == 1 {
// If there's exactly 1 hasher, then we have a plain StorageMap. We can't
// break the key down (even if it's a tuple) because the hasher applies to
// the whole key.
hashers_and_ty_ids = vec![(hashers[0], *key_ty)];
} else {
// If there are multiple hashers, then we have a StorageDoubleMap or StorageNMap.
// We expect the key type to be tuple, and we will return a MapEntryKey for each
// key in the tuple.
let hasher_count = hashers.len();
let tuple = match &ty.type_def {
TypeDef::Tuple(tuple) => tuple,
_ => {
return Err(StorageAddressError::WrongNumberOfHashers {
hashers: hasher_count,
fields: 1,
}
.into());
}
};
// We should have the same number of hashers and keys.
let key_count = tuple.fields.len();
if hasher_count != key_count {
return Err(StorageAddressError::WrongNumberOfHashers {
hashers: hasher_count,
fields: key_count,
}
.into());
}
// Collect them together.
hashers_and_ty_ids = tuple
.fields
.iter()
.zip(hashers)
.map(|(field, hasher)| (*hasher, field.id))
.collect();
}
if !cursor.is_empty() {
return Err(StorageKeyError::LeftoverBytes {
bytes: cursor.to_vec(),
});
}
Ok(Self { hashers_and_ty_ids })
}
/// Creates an iterator over the storage hashers and type ids.
pub fn iter(&self) -> StorageHashersIter<'_> {
StorageHashersIter {
hashers: self,
idx: 0,
}
}
}
/// An iterator over all type ids of the key and the respective hashers.
/// See [`StorageHashers::iter()`].
#[derive(Debug)]
pub struct StorageHashersIter<'a> {
hashers: &'a StorageHashers,
idx: usize,
}
impl StorageHashersIter<'_> {
fn next_or_err(&mut self) -> Result<(StorageHasher, u32), Error> {
self.next().ok_or_else(|| {
StorageAddressError::TooManyKeys {
expected: self.hashers.hashers_and_ty_ids.len(),
}
.into()
Ok(StorageKey {
info: Arc::new(storage_key_info),
types,
bytes,
marker: PhantomData,
})
}
}
impl Iterator for StorageHashersIter<'_> {
type Item = (StorageHasher, u32);
/// Attempt to decode the values contained within this storage key. The target type is
/// given by the storage address used to access this entry. To decode into a custom type,
/// use [`Self::parts()`] or [`Self::part()`] and decode each part.
pub fn decode(&self) -> Result<KeyParts, StorageKeyError> {
let values =
frame_decode::storage::decode_storage_key_values(&self.bytes, &self.info, self.types)
.map_err(StorageKeyError::CannotDecodeValuesInKey)?;
fn next(&mut self) -> Option<Self::Item> {
let item = self.hashers.hashers_and_ty_ids.get(self.idx).copied()?;
self.idx += 1;
Some(item)
Ok(values)
}
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
/// single value that has been hashed.
pub fn parts(&self) -> impl ExactSizeIterator<Item = StorageKeyPart<'info>> {
let parts_len = self.info.parts().len();
(0..parts_len).map(move |index| StorageKeyPart {
index,
info: self.info.clone(),
types: self.types,
bytes: self.bytes.clone(),
})
}
/// Return the part of the storage key at the provided index, or `None` if the index is out of bounds.
pub fn part(&self, index: usize) -> Option<StorageKeyPart<'info>> {
if index < self.parts().len() {
Some(StorageKeyPart {
index,
info: self.info.clone(),
types: self.types,
bytes: self.bytes.clone(),
})
} else {
None
}
}
}
impl ExactSizeIterator for StorageHashersIter<'_> {
fn len(&self) -> usize {
self.hashers.hashers_and_ty_ids.len() - self.idx
}
/// This represents a part of a storage key.
pub struct StorageKeyPart<'info> {
index: usize,
info: Arc<StorageKeyPartInfo<u32>>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
}
/// This trait should be implemented by anything that can be used as one or multiple storage keys.
pub trait StorageKey {
/// Encodes the storage key into some bytes
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error>;
/// Attempts to decode the StorageKey given some bytes and a set of hashers and type IDs that they are meant to represent.
/// The bytes passed to `decode` should start with:
/// - 1. some fixed size hash (for all hashers except `Identity`)
/// - 2. the plain key value itself (for `Identity`, `Blake2_128Concat` and `Twox64Concat` hashers)
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static;
}
/// Implement `StorageKey` for `()` which can be used for keyless storage entries,
/// or to otherwise just ignore some entry.
impl StorageKey for () {
fn encode_storage_key(
&self,
_bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
_types: &PortableRegistry,
) -> Result<(), Error> {
_ = hashers.next_or_err();
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error> {
let (hasher, ty_id) = match hashers.next_or_err() {
Ok((hasher, ty_id)) => (hasher, ty_id),
Err(_) if bytes.is_empty() => return Ok(()),
Err(err) => return Err(err),
impl<'info> StorageKeyPart<'info> {
/// Get the raw bytes for this part of the storage key.
pub fn bytes(&self) -> &[u8] {
let part = &self.info[self.index];
let hash_range = part.hash_range();
let value_range = part.value().map(|v| v.range()).unwrap_or(core::ops::Range {
start: hash_range.end,
end: hash_range.end,
});
let combined_range = core::ops::Range {
start: hash_range.start,
end: value_range.end,
};
consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
Ok(())
}
}
/// A storage key used as part of the static codegen.
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq)]
pub struct StaticStorageKey<K> {
key: K,
}
impl<K> StaticStorageKey<K> {
/// Creates a new static storage key.
pub fn new(key: K) -> Self {
StaticStorageKey { key }
}
}
impl<K: Clone> StaticStorageKey<K> {
/// Returns the decoded storage key.
pub fn into_key(self) -> K {
self.key
}
}
impl<K: EncodeAsType + DecodeAsType> StorageKey for StaticStorageKey<K> {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
let (hasher, ty_id) = hashers.next_or_err()?;
let encoded_value = self.key.encode_as_type(ty_id, types)?;
hash_bytes(&encoded_value, hasher, bytes);
Ok(())
&self.bytes[combined_range]
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
let (hasher, ty_id) = hashers.next_or_err()?;
let key_bytes = consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)?;
/// Get the hasher that was used to construct this part of the storage key.
pub fn hasher(&self) -> StorageHasher {
self.info[self.index].hasher()
}
// if the hasher had no key appended, we can't decode it into a `StaticStorageKey`.
let Some(key_bytes) = key_bytes else {
return Err(StorageAddressError::HasherCannotReconstructKey { ty_id, hasher }.into());
/// For keys that were produced using "concat" or "identity" hashers, the value
/// is available as a part of the key hash, allowing us to decode it into anything
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
/// different hasher, this will return `None`.
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
let part_info = &self.info[self.index];
let Some(value_info) = part_info.value() else {
return Ok(None);
};
// Decode and return the key.
let key = K::decode_as_type(&mut &*key_bytes, ty_id, types)?;
let key = StaticStorageKey { key };
Ok(key)
}
}
impl StorageKey for Vec<scale_value::Value> {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
for value in self.iter() {
let (hasher, ty_id) = hashers.next_or_err()?;
let encoded_value = value.encode_as_type(ty_id, types)?;
hash_bytes(&encoded_value, hasher, bytes);
}
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
let mut result: Vec<scale_value::Value> = vec![];
for (hasher, ty_id) in hashers.by_ref() {
match consume_hash_returning_key_bytes(bytes, hasher, ty_id, types)? {
Some(value_bytes) => {
let value =
scale_value::scale::decode_as_type(&mut &*value_bytes, ty_id, types)?;
result.push(value.remove_context());
}
None => {
result.push(Value::unnamed_composite([]));
}
}
}
// We've consumed all of the hashers, so we expect to also consume all of the bytes:
if !bytes.is_empty() {
return Err(StorageAddressError::TooManyBytes.into());
}
Ok(result)
}
}
// Skip over the hash bytes (including any key at the end), returning bytes
// representing the key if one exists, or None if the hasher has no key appended.
fn consume_hash_returning_key_bytes<'a>(
bytes: &mut &'a [u8],
hasher: StorageHasher,
ty_id: u32,
types: &PortableRegistry,
) -> Result<Option<&'a [u8]>, Error> {
// Strip the bytes off for the actual hash, consuming them.
let bytes_to_strip = hasher.len_excluding_key();
if bytes.len() < bytes_to_strip {
return Err(StorageAddressError::NotEnoughBytes.into());
}
*bytes = &bytes[bytes_to_strip..];
// Now, find the bytes representing the key, consuming them.
let before_key = *bytes;
if hasher.ends_with_key() {
scale_decode::visitor::decode_with_visitor(
bytes,
ty_id,
types,
IgnoreVisitor::<PortableRegistry>::new(),
)
.map_err(|err| Error::Decode(err.into()))?;
// Return the key bytes, having advanced the input cursor past them.
let key_bytes = &before_key[..before_key.len() - bytes.len()];
Ok(Some(key_bytes))
} else {
// There are no key bytes, so return None.
Ok(None)
}
}
/// Generates StorageKey implementations for tuples
macro_rules! impl_tuples {
($($ty:ident $n:tt),+) => {{
impl<$($ty: StorageKey),+> StorageKey for ($( $ty ),+) {
fn encode_storage_key(
&self,
bytes: &mut Vec<u8>,
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<(), Error> {
$( self.$n.encode_storage_key(bytes, hashers, types)?; )+
Ok(())
}
fn decode_storage_key(
bytes: &mut &[u8],
hashers: &mut StorageHashersIter,
types: &PortableRegistry,
) -> Result<Self, Error>
where
Self: Sized + 'static,
{
Ok( ( $( $ty::decode_storage_key(bytes, hashers, types)?, )+ ) )
}
}
}};
}
#[rustfmt::skip]
const _: () = {
impl_tuples!(A 0, B 1);
impl_tuples!(A 0, B 1, C 2);
impl_tuples!(A 0, B 1, C 2, D 3);
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
};
#[cfg(test)]
mod tests {
use codec::Encode;
use scale_info::{PortableRegistry, Registry, TypeInfo, meta_type};
use subxt_metadata::StorageHasher;
use crate::utils::Era;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use super::{StaticStorageKey, StorageKey};
struct KeyBuilder {
registry: Registry,
bytes: Vec<u8>,
hashers_and_ty_ids: Vec<(StorageHasher, u32)>,
}
impl KeyBuilder {
fn new() -> KeyBuilder {
KeyBuilder {
registry: Registry::new(),
bytes: vec![],
hashers_and_ty_ids: vec![],
}
}
fn add<T: TypeInfo + Encode + 'static>(mut self, value: T, hasher: StorageHasher) -> Self {
let id = self.registry.register_type(&meta_type::<T>()).id;
self.hashers_and_ty_ids.push((hasher, id));
for _i in 0..hasher.len_excluding_key() {
self.bytes.push(0);
}
value.encode_to(&mut self.bytes);
self
}
fn build(self) -> (PortableRegistry, Vec<u8>, Vec<(StorageHasher, u32)>) {
(self.registry.into(), self.bytes, self.hashers_and_ty_ids)
}
}
#[test]
fn storage_key_decoding_fuzz() {
let hashers = [
StorageHasher::Blake2_128,
StorageHasher::Blake2_128Concat,
StorageHasher::Blake2_256,
StorageHasher::Identity,
StorageHasher::Twox128,
StorageHasher::Twox256,
StorageHasher::Twox64Concat,
];
let key_preserving_hashers = [
StorageHasher::Blake2_128Concat,
StorageHasher::Identity,
StorageHasher::Twox64Concat,
];
type T4A = (
(),
StaticStorageKey<u32>,
StaticStorageKey<String>,
StaticStorageKey<Era>,
);
type T4B = (
(),
(StaticStorageKey<u32>, StaticStorageKey<String>),
StaticStorageKey<Era>,
);
type T4C = (
((), StaticStorageKey<u32>),
(StaticStorageKey<String>, StaticStorageKey<Era>),
);
let era = Era::Immortal;
for h0 in hashers {
for h1 in key_preserving_hashers {
for h2 in key_preserving_hashers {
for h3 in key_preserving_hashers {
let (types, bytes, hashers_and_ty_ids) = KeyBuilder::new()
.add((), h0)
.add(13u32, h1)
.add("Hello", h2)
.add(era, h3)
.build();
let hashers = super::StorageHashers { hashers_and_ty_ids };
let keys_a =
T4A::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
let keys_b =
T4B::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
let keys_c =
T4C::decode_storage_key(&mut &bytes[..], &mut hashers.iter(), &types)
.unwrap();
assert_eq!(keys_a.1.into_key(), 13);
assert_eq!(keys_b.1.0.into_key(), 13);
assert_eq!(keys_c.0.1.into_key(), 13);
assert_eq!(keys_a.2.into_key(), "Hello");
assert_eq!(keys_b.1.1.into_key(), "Hello");
assert_eq!(keys_c.1.0.into_key(), "Hello");
assert_eq!(keys_a.3.into_key(), era);
assert_eq!(keys_b.2.into_key(), era);
assert_eq!(keys_c.1.1.into_key(), era);
}
}
}
}
let value_bytes = &self.bytes[value_info.range()];
let value_ty = *value_info.ty();
let decoded_key_part = T::decode_as_type(&mut &*value_bytes, value_ty, self.types)
.map_err(|e| StorageKeyError::CannotDecodeValueInKey {
index: self.index,
error: e,
})?;
Ok(Some(decoded_key_part))
}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{Address, StorageKey, StorageValue};
use crate::error::StorageKeyError;
use alloc::sync::Arc;
use alloc::vec::Vec;
use frame_decode::storage::StorageInfo;
use scale_info::PortableRegistry;
/// This represents a storage key/value pair, which is typically returned from
/// iterating over values in some storage map.
#[derive(Debug)]
pub struct StorageKeyValue<'info, Addr: Address> {
key: Arc<[u8]>,
// This contains the storage information already:
value: StorageValue<'info, Addr::Value>,
}
impl<'info, Addr: Address> StorageKeyValue<'info, Addr> {
pub(crate) fn new(
info: Arc<StorageInfo<'info, u32>>,
types: &'info PortableRegistry,
key_bytes: Arc<[u8]>,
value_bytes: Vec<u8>,
) -> Self {
StorageKeyValue {
key: key_bytes,
value: StorageValue::new(info, types, value_bytes),
}
}
/// Get the raw bytes for this storage entry's key.
pub fn key_bytes(&self) -> &[u8] {
&self.key
}
/// Decode the key for this storage entry. This gives back a type from which we can
/// decode specific parts of the key hash (where applicable).
pub fn key(&'_ self) -> Result<StorageKey<'info, Addr::KeyParts>, StorageKeyError> {
StorageKey::new(&self.value.info, self.value.types, self.key.clone())
}
/// Return the storage value.
pub fn value(&self) -> &StorageValue<'info, Addr::Value> {
&self.value
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::StorageValueError;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::marker::PhantomData;
use frame_decode::storage::StorageInfo;
use scale_decode::DecodeAsType;
use scale_info::PortableRegistry;
/// This represents a storage value.
#[derive(Debug)]
pub struct StorageValue<'info, Value> {
pub(crate) info: Arc<StorageInfo<'info, u32>>,
pub(crate) types: &'info PortableRegistry,
bytes: Vec<u8>,
marker: PhantomData<Value>,
}
impl<'info, Value: DecodeAsType> StorageValue<'info, Value> {
pub(crate) fn new(
info: Arc<StorageInfo<'info, u32>>,
types: &'info PortableRegistry,
bytes: Vec<u8>,
) -> StorageValue<'info, Value> {
StorageValue {
info,
types,
bytes,
marker: PhantomData,
}
}
/// Get the raw bytes for this storage value.
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
/// Consume this storage value and return the raw bytes.
pub fn into_bytes(self) -> Vec<u8> {
self.bytes.to_vec()
}
/// Decode this storage value into the provided response type.
pub fn decode(&self) -> Result<Value, StorageValueError> {
self.decode_as::<Value>()
}
/// Decode this storage value into an arbitrary type.
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
let cursor = &mut &*self.bytes;
let value = frame_decode::storage::decode_storage_value_with_info(
cursor,
&self.info,
self.types,
T::into_visitor(),
)
.map_err(StorageValueError::CannotDecode)?;
if !cursor.is_empty() {
return Err(StorageValueError::LeftoverBytes {
bytes: cursor.to_vec(),
});
}
Ok(value)
}
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2019-2024 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 [`Address`] trait, but
//! aren't things that should ever be overridden, and so don't exist on
//! the trait itself.
use super::address::Address;
use crate::error::{Error, MetadataError};
use crate::metadata::Metadata;
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageHasher};
/// Return the root of a given [`Address`]: hash the pallet name and entry name
/// and append those bytes to the output.
pub fn write_storage_address_root_bytes<Addr: Address>(addr: &Addr, out: &mut Vec<u8>) {
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
}
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
match hasher {
StorageHasher::Identity => bytes.extend(input),
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
StorageHasher::Blake2_128Concat => {
bytes.extend(sp_crypto_hashing::blake2_128(input));
bytes.extend(input);
}
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
StorageHasher::Twox64Concat => {
bytes.extend(sp_crypto_hashing::twox_64(input));
bytes.extend(input);
}
}
}
/// Return details about the given storage entry.
pub fn lookup_storage_entry_details<'a>(
pallet_name: &str,
entry_name: &str,
metadata: &'a Metadata,
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
let storage_metadata = pallet_metadata
.storage()
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
let storage_entry = storage_metadata
.entry_by_name(entry_name)
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
Ok((pallet_metadata, storage_entry))
}
+37 -23
View File
@@ -13,7 +13,7 @@
//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
//! use subxt_core::tx;
//! use subxt_core::utils::H256;
//! use subxt_core::metadata;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
@@ -26,7 +26,7 @@
//! let state = tx::ClientState::<PolkadotConfig> {
//! metadata: {
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! metadata::decode_from(&metadata_bytes[..]).unwrap()
//! Metadata::decode_from(&metadata_bytes[..]).unwrap()
//! },
//! genesis_hash: {
//! let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
@@ -59,11 +59,12 @@
pub mod payload;
pub mod signer;
use crate::Metadata;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
use crate::error::{Error, ExtrinsicError, MetadataError};
use crate::metadata::Metadata;
use crate::error::ExtrinsicError;
use crate::utils::Encoded;
use alloc::borrow::{Cow, ToOwned};
use alloc::borrow::Cow;
use alloc::string::ToString;
use alloc::vec::Vec;
use codec::{Compact, Encode};
use payload::Payload;
@@ -77,18 +78,28 @@ pub use crate::client::{ClientState, RuntimeVersion};
/// 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: Payload>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
if let Some(details) = call.validation_details() {
let expected_hash = metadata
.pallet_by_name_err(details.pallet_name)?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), ExtrinsicError> {
let Some(details) = call.validation_details() else {
return Ok(());
};
if details.hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
let pallet_name = details.pallet_name;
let call_name = details.call_name;
let expected_hash = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(pallet_name.to_string()))?
.call_hash(call_name)
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
pallet_name: pallet_name.to_string(),
call_name: call_name.to_string(),
})?;
if details.hash != expected_hash {
Err(ExtrinsicError::IncompatibleCodegen)
} else {
Ok(())
}
Ok(())
}
/// Returns the suggested transaction versions to build for a given chain, or an error
@@ -96,7 +107,7 @@ pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), E
///
/// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's
/// [`TransactionVersion::V5`], use the `v5` ones.
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Error> {
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, ExtrinsicError> {
let versions = metadata.extrinsic().supported_versions();
if versions.contains(&4) {
@@ -104,7 +115,7 @@ pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Erro
} else if versions.contains(&5) {
Ok(TransactionVersion::V5)
} else {
Err(ExtrinsicError::UnsupportedVersion.into())
Err(ExtrinsicError::UnsupportedVersion)
}
}
@@ -118,7 +129,10 @@ pub enum TransactionVersion {
}
/// Return the SCALE encoded bytes representing the call data of the transaction.
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
pub fn call_data<Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Vec<u8>, ExtrinsicError> {
let mut bytes = Vec::new();
call.encode_call_data_to(metadata, &mut bytes)?;
Ok(bytes)
@@ -128,7 +142,7 @@ pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<
pub fn create_v4_unsigned<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 4, metadata)
}
@@ -136,7 +150,7 @@ pub fn create_v4_unsigned<T: Config, Call: Payload>(
pub fn create_v5_bare<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 5, metadata)
}
@@ -145,7 +159,7 @@ fn create_unsigned_at_version<T: Config, Call: Payload>(
call: &Call,
tx_version: u8,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
) -> Result<Transaction<T>, ExtrinsicError> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, metadata)?;
@@ -176,7 +190,7 @@ pub fn create_v4_signed<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV4<T>, Error> {
) -> Result<PartialTransactionV4<T>, ExtrinsicError> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
@@ -200,7 +214,7 @@ pub fn create_v5_general<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV5<T>, Error> {
) -> Result<PartialTransactionV5<T>, ExtrinsicError> {
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
validate(call, &client_state.metadata)?;
+27 -14
View File
@@ -5,12 +5,11 @@
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::Error;
use crate::error::MetadataError;
use crate::metadata::Metadata;
use alloc::borrow::{Cow, ToOwned};
use crate::Metadata;
use crate::error::ExtrinsicError;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use codec::Encode;
@@ -21,11 +20,15 @@ use scale_value::{Composite, Value, ValueDef, Variant};
/// to a node.
pub trait Payload {
/// Encode call data to the provided output.
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicError>;
/// Encode call data and return the output. This is a convenience
/// wrapper around [`Payload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
@@ -46,10 +49,10 @@ macro_rules! boxed_payload {
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
) -> Result<(), ExtrinsicError> {
self.as_ref().encode_call_data_to(metadata, out)
}
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
self.as_ref().encode_call_data(metadata)
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
@@ -164,11 +167,20 @@ impl DefaultPayload<Composite<()>> {
}
impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicError> {
let pallet = metadata
.pallet_by_name(&self.pallet_name)
.ok_or_else(|| ExtrinsicError::PalletNameNotFound(self.pallet_name.to_string()))?;
let call = pallet
.call_variant_by_name(&self.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
.ok_or_else(|| ExtrinsicError::CallNameNotFound {
pallet_name: pallet.name().to_string(),
call_name: self.call_name.to_string(),
})?;
let pallet_index = pallet.index();
let call_index = call.index;
@@ -182,7 +194,8 @@ impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
.map(|f| scale_encode::Field::new(f.ty.id, f.name.as_deref()));
self.call_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
.encode_as_fields_to(&mut fields, metadata.types(), out)
.map_err(ExtrinsicError::CannotEncodeCallData)?;
Ok(())
}
@@ -208,7 +221,7 @@ pub fn dynamic(
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::Metadata;
use crate::Metadata;
use codec::Decode;
use scale_value::Composite;
+2 -3
View File
@@ -13,6 +13,7 @@ mod multi_signature;
mod static_type;
mod unchecked_extrinsic;
mod wrapper_opaque;
mod yesnomaybe;
use alloc::borrow::ToOwned;
use alloc::format;
@@ -30,6 +31,7 @@ pub use primitive_types::{H160, H256, H512};
pub use static_type::Static;
pub use unchecked_extrinsic::UncheckedExtrinsic;
pub use wrapper_opaque::WrapperKeepOpaque;
pub use yesnomaybe::{Maybe, No, NoMaybe, Yes, YesMaybe, YesNo};
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
@@ -73,9 +75,6 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
/// A unit marker struct.
pub struct Yes;
/// A quick helper to encode some bytes to hex.
pub fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
/// A unit marker enum.
pub enum Yes {}
/// A unit marker enum.
pub enum Maybe {}
/// A unit marker enum.
pub enum No {}
/// This is implemented for [`Yes`] and [`No`] and
/// allows us to check at runtime which of these types is present.
pub trait YesNo {
/// [`Yes`]
fn is_yes() -> bool {
false
}
/// [`No`]
fn is_no() -> bool {
false
}
}
impl YesNo for Yes {
fn is_yes() -> bool {
true
}
}
impl YesNo for No {
fn is_no() -> bool {
true
}
}
/// This is implemented for [`Yes`] and [`Maybe`] and
/// allows us to check at runtime which of these types is present.
pub trait YesMaybe {
/// [`Yes`]
fn is_yes() -> bool {
false
}
/// [`Maybe`]
fn is_maybe() -> bool {
false
}
}
impl YesMaybe for Yes {
fn is_yes() -> bool {
true
}
}
impl YesMaybe for Maybe {
fn is_maybe() -> bool {
true
}
}
/// This is implemented for [`No`] and [`Maybe`] and
/// allows us to check at runtime which of these types is present.
pub trait NoMaybe {
/// [`No`]
fn is_no() -> bool {
false
}
/// [`Maybe`]
fn is_maybe() -> bool {
false
}
}
impl NoMaybe for No {
fn is_no() -> bool {
true
}
}
impl NoMaybe for Maybe {
fn is_maybe() -> bool {
true
}
}
+46 -32
View File
@@ -7,28 +7,39 @@
pub mod payload;
use crate::error::{Error, MetadataError};
use crate::metadata::{DecodeWithMetadata, Metadata};
use crate::Metadata;
use crate::error::ViewFunctionError;
use alloc::string::ToString;
use alloc::vec::Vec;
use payload::Payload;
use scale_decode::IntoVisitor;
/// Run the validation logic against some View Function payload you'd like to use. Returns `Ok(())`
/// if the payload is valid (or if it's not possible to check since the payload has no validation hash).
/// Return an error if the payload was not valid or something went wrong trying to validate it (ie
/// the View Function in question do not exist at all)
pub fn validate<P: Payload>(payload: &P, metadata: &Metadata) -> Result<(), Error> {
let Some(static_hash) = payload.validation_hash() else {
pub fn validate<P: Payload>(payload: P, metadata: &Metadata) -> Result<(), ViewFunctionError> {
let Some(hash) = payload.validation_hash() else {
return Ok(());
};
let view_function = metadata
.view_function_by_query_id(payload.query_id())
.ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?;
if static_hash != view_function.hash() {
return Err(MetadataError::IncompatibleCodegen.into());
}
let pallet_name = payload.pallet_name();
let function_name = payload.function_name();
Ok(())
let view_function = metadata
.pallet_by_name(pallet_name)
.ok_or_else(|| ViewFunctionError::PalletNotFound(pallet_name.to_string()))?
.view_function_by_name(function_name)
.ok_or_else(|| ViewFunctionError::ViewFunctionNotFound {
pallet_name: pallet_name.to_string(),
function_name: function_name.to_string(),
})?;
if hash != view_function.hash() {
Err(ViewFunctionError::IncompatibleCodegen)
} else {
Ok(())
}
}
/// The name of the Runtime API call which can execute
@@ -36,34 +47,37 @@ pub const CALL_NAME: &str = "RuntimeViewFunction_execute_view_function";
/// Encode the bytes that will be passed to the "execute_view_function" Runtime API call,
/// to execute the View Function represented by the given payload.
pub fn call_args<P: Payload>(payload: &P, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut call_args = Vec::with_capacity(32);
call_args.extend_from_slice(payload.query_id());
pub fn call_args<P: Payload>(
payload: P,
metadata: &Metadata,
) -> Result<Vec<u8>, ViewFunctionError> {
let inputs = frame_decode::view_functions::encode_view_function_inputs(
payload.pallet_name(),
payload.function_name(),
payload.args(),
metadata,
metadata.types(),
)
.map_err(ViewFunctionError::CouldNotEncodeInputs)?;
let mut call_arg_params = Vec::new();
payload.encode_args_to(metadata, &mut call_arg_params)?;
use codec::Encode;
call_arg_params.encode_to(&mut call_args);
Ok(call_args)
Ok(inputs)
}
/// Decode the value bytes at the location given by the provided View Function payload.
pub fn decode_value<P: Payload>(
bytes: &mut &[u8],
payload: &P,
payload: P,
metadata: &Metadata,
) -> Result<P::ReturnType, Error> {
let view_function = metadata
.view_function_by_query_id(payload.query_id())
.ok_or_else(|| MetadataError::ViewFunctionNotFound(*payload.query_id()))?;
let val = <P::ReturnType as DecodeWithMetadata>::decode_with_metadata(
&mut &bytes[..],
view_function.output_ty(),
) -> Result<P::ReturnType, ViewFunctionError> {
let value = frame_decode::view_functions::decode_view_function_response(
payload.pallet_name(),
payload.function_name(),
bytes,
metadata,
)?;
metadata.types(),
P::ReturnType::into_visitor(),
)
.map_err(ViewFunctionError::CouldNotDecodeResponse)?;
Ok(val)
Ok(value)
}
+80 -68
View File
@@ -5,17 +5,12 @@
//! This module contains the trait and types used to represent
//! View Function calls that can be made.
use alloc::vec::Vec;
use alloc::borrow::Cow;
use alloc::string::String;
use core::marker::PhantomData;
use derive_where::derive_where;
use scale_encode::EncodeAsFields;
use scale_value::Composite;
use crate::Error;
use crate::dynamic::DecodedValueThunk;
use crate::error::MetadataError;
use crate::metadata::{DecodeWithMetadata, Metadata};
use frame_decode::view_functions::IntoEncodableValues;
use scale_decode::DecodeAsType;
/// This represents a View Function payload that can call into the runtime of node.
///
@@ -33,24 +28,19 @@ use crate::metadata::{DecodeWithMetadata, Metadata};
///
/// Each argument of the View Function must be scale-encoded.
pub trait Payload {
/// Type of the arguments for this call.
type ArgsType: IntoEncodableValues;
/// The return type of the function call.
// Note: `DecodeWithMetadata` is needed to decode the function call result
// with the `subxt::Metadata.
type ReturnType: DecodeWithMetadata;
type ReturnType: DecodeAsType;
/// The payload target.
fn query_id(&self) -> &[u8; 32];
/// The View Function pallet name.
fn pallet_name(&self) -> &str;
/// Scale encode the arguments data.
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
/// The View Function function name.
fn function_name(&self) -> &str;
/// Encode arguments data and return the output. This is a convenience
/// wrapper around [`Payload::encode_args_to`].
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_args_to(metadata, &mut v)?;
Ok(v)
}
/// The arguments.
fn args(&self) -> &Self::ArgsType;
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -58,45 +48,61 @@ pub trait Payload {
}
}
// A reference to a payload is a valid payload.
impl<P: Payload + ?Sized> Payload for &'_ P {
type ArgsType = P::ArgsType;
type ReturnType = P::ReturnType;
fn pallet_name(&self) -> &str {
P::pallet_name(*self)
}
fn function_name(&self) -> &str {
P::function_name(*self)
}
fn args(&self) -> &Self::ArgsType {
P::args(*self)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
P::validation_hash(*self)
}
}
/// A View Function payload containing the generic argument data
/// and interpreting the result of the call as `ReturnTy`.
/// and interpreting the result of the call as `ReturnType`.
///
/// This can be created from static values (ie those generated
/// via the `subxt` macro) or dynamic values via [`dynamic`].
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
pub struct DefaultPayload<ArgsData, ReturnTy> {
query_id: [u8; 32],
args_data: ArgsData,
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsType)]
pub struct StaticPayload<ArgsType, ReturnType> {
pallet_name: Cow<'static, str>,
function_name: Cow<'static, str>,
args: ArgsType,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnTy>,
_marker: PhantomData<ReturnType>,
}
/// A statically generated View Function payload.
pub type StaticPayload<ArgsData, ReturnTy> = DefaultPayload<ArgsData, ReturnTy>;
/// A dynamic View Function payload.
pub type DynamicPayload = DefaultPayload<Composite<()>, DecodedValueThunk>;
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
for DefaultPayload<ArgsData, ReturnTy>
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
type ReturnType = ReturnTy;
type ArgsType = ArgsType;
type ReturnType = ReturnType;
fn query_id(&self) -> &[u8; 32] {
&self.query_id
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
let view_function = metadata
.view_function_by_query_id(&self.query_id)
.ok_or(MetadataError::ViewFunctionNotFound(self.query_id))?;
let mut fields = view_function
.inputs()
.map(|input| scale_encode::Field::named(input.ty, &input.name));
fn function_name(&self) -> &str {
&self.function_name
}
self.args_data
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
Ok(())
fn args(&self) -> &Self::ArgsType {
&self.args
}
fn validation_hash(&self) -> Option<[u8; 32]> {
@@ -104,30 +110,37 @@ impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> Payload
}
}
impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
/// Create a new [`DefaultPayload`] for a View Function call.
pub fn new(query_id: [u8; 32], args_data: ArgsData) -> Self {
DefaultPayload {
query_id,
args_data,
impl<ReturnTy, ArgsType> StaticPayload<ArgsType, ReturnTy> {
/// Create a new [`StaticPayload`] for a View Function call.
pub fn new(
pallet_name: impl Into<String>,
function_name: impl Into<String>,
args: ArgsType,
) -> Self {
StaticPayload {
pallet_name: pallet_name.into().into(),
function_name: function_name.into().into(),
args,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`DefaultPayload`] for a View Function call
/// Create a new static [`StaticPayload`] for a View Function call
/// using static function name and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
query_id: [u8; 32],
args_data: ArgsData,
pallet_name: &'static str,
function_name: &'static str,
args: ArgsType,
hash: [u8; 32],
) -> DefaultPayload<ArgsData, ReturnTy> {
DefaultPayload {
query_id,
args_data,
) -> StaticPayload<ArgsType, ReturnTy> {
StaticPayload {
pallet_name: Cow::Borrowed(pallet_name),
function_name: Cow::Borrowed(function_name),
args,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
@@ -140,14 +153,13 @@ impl<ReturnTy, ArgsData> DefaultPayload<ArgsData, ReturnTy> {
..self
}
}
/// Returns the arguments data.
pub fn args_data(&self) -> &ArgsData {
&self.args_data
}
}
/// Create a new [`DynamicPayload`] to call a View Function.
pub fn dynamic(query_id: [u8; 32], args_data: impl Into<Composite<()>>) -> DynamicPayload {
DefaultPayload::new(query_id, args_data.into())
pub fn dynamic<ArgsType, ReturnType>(
pallet_name: impl Into<String>,
function_name: impl Into<String>,
args: ArgsType,
) -> DynamicPayload<ArgsType, ReturnType> {
DynamicPayload::new(pallet_name, function_name, args)
}
+60 -12
View File
@@ -475,6 +475,12 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_format"
version = "0.2.34"
@@ -660,6 +666,17 @@ dependencies = [
"syn",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "derive-where"
version = "1.5.0"
@@ -756,21 +773,23 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-zebra"
version = "4.0.3"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9"
checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9"
dependencies = [
"curve25519-dalek",
"ed25519",
"hashbrown 0.14.5",
"hex",
"hashbrown 0.15.4",
"pkcs8",
"rand_core",
"sha2 0.10.9",
"subtle",
"zeroize",
]
@@ -864,9 +883,9 @@ dependencies = [
[[package]]
name = "frame-decode"
version = "0.9.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
checksum = "a0acacffe60911b0d57a55c0b323fc08ccd88659dc52056bb39dfeb5cedafb59"
dependencies = [
"frame-metadata",
"parity-scale-codec",
@@ -1751,6 +1770,15 @@ dependencies = [
"password-hash",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -1800,6 +1828,16 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "polling"
version = "3.8.0"
@@ -2262,9 +2300,9 @@ dependencies = [
[[package]]
name = "scale-value"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
dependencies = [
"base58",
"blake2",
@@ -2527,9 +2565,9 @@ dependencies = [
[[package]]
name = "smoldot"
version = "0.19.4"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16e5723359f0048bf64bfdfba64e5732a56847d42c4fd3fe56f18280c813413"
checksum = "724ab10d6485cccb4bab080ce436c0b361295274aec7847d7ba84ab1a79a5132"
dependencies = [
"arrayvec 0.7.6",
"async-lock",
@@ -2581,9 +2619,9 @@ dependencies = [
[[package]]
name = "smoldot-light"
version = "0.17.2"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bba9e591716567d704a8252feeb2f1261a286e1e2cbdd4e49e9197c34a14e2"
checksum = "e8b4d4971f06f2471f4e57a662dbe8047fa0cc020957764a6211f3fad371f7bd"
dependencies = [
"async-channel",
"async-lock",
@@ -2660,6 +2698,16 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
+3 -2
View File
@@ -53,12 +53,13 @@ pub extern "C" fn do_transfer(dest_hex: *const c_char, amount: u64) -> i32 {
);
// Submit and wait for finalize
let res: Result<_, subxt::Error> = tokio_rt().block_on(async {
let res: Result<(), subxt::Error> = tokio_rt().block_on(async {
let progress = client
.tx()
.sign_and_submit_then_watch_default(&tx, &signer)
.await?;
progress.wait_for_finalized_success().await
progress.wait_for_finalized_success().await?;
Ok(())
});
// Return code
+73 -16
View File
@@ -504,6 +504,12 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_format"
version = "0.2.34"
@@ -689,6 +695,17 @@ dependencies = [
"syn",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "derive-where"
version = "1.2.7"
@@ -785,21 +802,23 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-zebra"
version = "4.0.3"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9"
checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9"
dependencies = [
"curve25519-dalek",
"ed25519",
"hashbrown 0.14.5",
"hex",
"hashbrown 0.15.2",
"pkcs8",
"rand_core",
"sha2 0.10.8",
"subtle",
"zeroize",
]
@@ -903,6 +922,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -914,9 +939,9 @@ dependencies = [
[[package]]
name = "frame-decode"
version = "0.9.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
checksum = "a0acacffe60911b0d57a55c0b323fc08ccd88659dc52056bb39dfeb5cedafb59"
dependencies = [
"frame-metadata",
"parity-scale-codec",
@@ -1096,6 +1121,9 @@ version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
"serde",
]
@@ -1823,6 +1851,15 @@ dependencies = [
"password-hash",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -1872,6 +1909,16 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "polling"
version = "3.3.2"
@@ -2329,9 +2376,9 @@ dependencies = [
[[package]]
name = "scale-value"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
dependencies = [
"base58",
"blake2",
@@ -2598,9 +2645,9 @@ dependencies = [
[[package]]
name = "smoldot"
version = "0.19.4"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16e5723359f0048bf64bfdfba64e5732a56847d42c4fd3fe56f18280c813413"
checksum = "724ab10d6485cccb4bab080ce436c0b361295274aec7847d7ba84ab1a79a5132"
dependencies = [
"arrayvec 0.7.6",
"async-lock 3.3.0",
@@ -2652,9 +2699,9 @@ dependencies = [
[[package]]
name = "smoldot-light"
version = "0.17.2"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bba9e591716567d704a8252feeb2f1261a286e1e2cbdd4e49e9197c34a14e2"
checksum = "e8b4d4971f06f2471f4e57a662dbe8047fa0cc020957764a6211f3fad371f7bd"
dependencies = [
"async-channel",
"async-lock 3.3.0",
@@ -2731,6 +2778,16 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -2751,9 +2808,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.5.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "subxt"
@@ -3711,9 +3768,9 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.7.0"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
dependencies = [
"zeroize_derive",
]
+3 -3
View File
@@ -63,14 +63,14 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
println!("NFT created.");
// check in storage, that alice is the official owner of the NFT:
let nft_owner_storage_query = statemint::storage().uniques().asset(COLLECTION_ID, NTF_ID);
let nft_owner_storage_query = statemint::storage().uniques().asset();
let nft_storage_details = api
.storage()
.at_latest()
.await?
.fetch(&nft_owner_storage_query)
.fetch(nft_owner_storage_query, (COLLECTION_ID, NTF_ID))
.await?
.ok_or("The NFT should have an owner (alice)")?;
.decode()?;
// make sure that alice is the owner of the NFT:
assert_eq!(nft_storage_details.owner, dev::alice().public_key().into());
+4 -4
View File
@@ -667,9 +667,9 @@ dependencies = [
[[package]]
name = "frame-decode"
version = "0.9.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd"
checksum = "a0acacffe60911b0d57a55c0b323fc08ccd88659dc52056bb39dfeb5cedafb59"
dependencies = [
"frame-metadata",
"parity-scale-codec",
@@ -2114,9 +2114,9 @@ dependencies = [
[[package]]
name = "scale-value"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca8b26b451ecb7fd7b62b259fa28add63d12ec49bbcac0e01fcb4b5ae0c09aa"
checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3"
dependencies = [
"base58",
"blake2",
+2 -2
View File
@@ -28,7 +28,7 @@ pub(crate) async fn fetch_events_dynamically() -> Result<Vec<String>, subxt::Err
let event = event?;
let pallet = event.pallet_name();
let variant = event.variant_name();
let field_values = event.field_values()?;
let field_values = event.decode_as_fields::<subxt::dynamic::Value>()?;
event_strings.push(format!("{pallet}::{variant}: {field_values}"));
}
Ok(event_strings)
@@ -66,7 +66,7 @@ pub(crate) async fn subscribe_to_finalized_blocks(
let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.field_values()?;
let event_values = evt.decode_as_fields::<subxt::dynamic::Value>()?;
writeln!(output, " {pallet_name}_{event_name}").ok();
writeln!(output, " {}", event_values).ok();
+4 -4
View File
@@ -43,7 +43,7 @@ async fn main() -> Result<(), Error> {
println!(
" {}: {}",
field.name(),
field.decode::<scale_value::Value>().unwrap()
field.decode_as::<scale_value::Value>().unwrap()
);
}
@@ -53,7 +53,7 @@ async fn main() -> Result<(), Error> {
extrinsic
.call()
.fields()
.decode::<scale_value::Composite<_>>()
.decode_as::<scale_value::Composite<_>>()
.unwrap()
);
@@ -66,14 +66,14 @@ async fn main() -> Result<(), Error> {
println!(
" {}: {}",
extension.name(),
extension.decode::<scale_value::Value>().unwrap()
extension.decode_as::<scale_value::Value>().unwrap()
);
}
// Or all of them at once:
println!(
" All: {}",
extensions.decode::<scale_value::Composite<_>>().unwrap()
extensions.decode_as::<scale_value::Composite<_>>().unwrap()
);
}
}
+24 -12
View File
@@ -19,25 +19,22 @@ async fn main() -> Result<(), Error> {
let client_at_block = client.at(block_number).await?;
// We'll work the account balances at the given block, for this example.
let account_balances = client_at_block
.storage()
.entry("System", "Account")?
.into_map()?;
let account_balances = client_at_block.storage().entry("System", "Account")?;
// We can see the default value for this entry at this block, if one exists.
if let Some(default_value) = account_balances.default() {
let default_balance_info = default_value.decode::<scale_value::Value>()?;
if let Some(default_value) = account_balances.default_value() {
let default_balance_info = default_value.decode_as::<scale_value::Value>()?;
println!(" Default balance info: {default_balance_info}");
}
// We can fetch a specific account balance by its key, like so (here I just picked a random key
// I knew to exist from iterating over storage entries):
let account_id_hex = "9a4d0faa2ba8c3cc5711852960940793acf55bf195b6eecf88fa78e961d0ce4a";
let account_id = hex::decode(account_id_hex).unwrap();
let account_id: [u8; 32] = hex::decode(account_id_hex).unwrap().try_into().unwrap();
if let Some(entry) = account_balances.fetch((account_id,)).await? {
// We can decode the value into our generic `scale_value::Value` type, which can
// represent any SCALE-encoded value, like so:
let _balance_info = entry.decode::<scale_value::Value>()?;
let _balance_info = entry.decode_as::<scale_value::Value>()?;
// Or, if we know what shape to expect, we can decode the parts of the value that we care
// about directly into a static type, which is more efficient and allows easy type-safe
@@ -53,7 +50,7 @@ async fn main() -> Result<(), Error> {
misc_frozen: u128,
fee_frozen: u128,
}
let balance_info = entry.decode::<BalanceInfo>()?;
let balance_info = entry.decode_as::<BalanceInfo>()?;
println!(
" Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {}",
@@ -72,23 +69,38 @@ async fn main() -> Result<(), Error> {
let mut all_balances = account_balances.iter(()).await?.take(10);
while let Some(entry) = all_balances.next().await {
let entry = entry?;
let key = entry.decode_key()?;
let key = entry.key()?;
// Decode the account ID from the key (we know here that we're working
// with a map which has one value, an account ID, so we just decode that part:
let account_id = key
.part(0)
.unwrap()
.decode::<[u8; 32]>()?
.decode_as::<[u8; 32]>()?
.expect("We expect this key to decode into a 32 byte AccountId");
let account_id_hex = hex::encode(account_id);
// Decode these values into our generic scale_value::Value type. Less efficient than
// defining a static type as above, but easier for the sake of the example.
let balance_info = entry.decode_value::<scale_value::Value>()?;
let balance_info = entry.value().decode_as::<scale_value::Value>()?;
println!(" {account_id_hex} => {balance_info}");
}
// We can also chain things together to fetch and decode a value in one go.
let _val = client_at_block
.storage()
.entry("System", "Account")?
.fetch((account_id,))
.await?
.unwrap()
.decode_as::<scale_value::Value>()?;
let _vals = client_at_block
.storage()
.entry("System", "Account")?
.iter(())
.await?;
}
Ok(())
+19 -10
View File
@@ -21,10 +21,6 @@ pub enum Error {
#[error(transparent)]
StorageError(#[from] StorageError),
#[error(transparent)]
StorageEntryIsNotAMap(#[from] StorageEntryIsNotAMap),
#[error(transparent)]
StorageEntryIsNotAPlainValue(#[from] StorageEntryIsNotAPlainValue),
#[error(transparent)]
StorageKeyError(#[from] StorageKeyError),
#[error(transparent)]
StorageValueError(#[from] StorageValueError),
@@ -214,22 +210,22 @@ pub enum ExtrinsicCallError {
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[error("Storage entry is not a map: pallet {pallet_name}, storage {storage_name}")]
#[error("Storage entry is not a map: pallet {pallet_name}, storage {entry_name}")]
pub struct StorageEntryIsNotAMap {
/// The pallet containing the storage entry that was not found.
pub pallet_name: String,
/// The storage entry that was not found.
pub storage_name: String,
pub entry_name: String,
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {storage_name}")]
#[error("Storage entry is not a plain value: pallet {pallet_name}, storage {entry_name}")]
pub struct StorageEntryIsNotAPlainValue {
/// The pallet containing the storage entry that was not found.
pub pallet_name: String,
/// The storage entry that was not found.
pub storage_name: String,
pub entry_name: String,
}
#[allow(missing_docs)]
@@ -252,14 +248,23 @@ pub enum StorageError {
reason: frame_decode::storage::StorageKeyEncodeError,
},
#[error(
"Too many keys provided: expected {num_keys_expected} keys, but got {num_keys_provided}"
"Wrong number of keys provided to fetch a value: expected {num_keys_expected} keys, but got {num_keys_provided}"
)]
WrongNumberOfKeysProvided {
WrongNumberOfKeysProvidedForFetch {
/// The number of keys that were provided.
num_keys_provided: usize,
/// The number of keys expected.
num_keys_expected: usize,
},
#[error(
"too many keys were provided to iterate over a storage entry: expected at most {max_keys_expected} keys, but got {num_keys_provided}"
)]
TooManyKeysProvidedForIter {
/// The number of keys that were provided.
num_keys_provided: usize,
/// The maximum number of keys that we expect.
max_keys_expected: usize,
},
#[error(
"Could not extract storage information from metadata: Unsupported metadata version ({version})"
)]
@@ -295,6 +300,10 @@ pub enum StorageKeyError {
index: usize,
reason: scale_decode::Error,
},
#[error("Could not decode values out of the storage key: {reason}")]
DecodeKeyValueError {
reason: frame_decode::storage::StorageKeyValueDecodeError,
},
}
#[allow(missing_docs)]
+1 -1
View File
@@ -7,7 +7,7 @@ mod extrinsic_info;
mod extrinsic_transaction_extensions;
mod extrinsics_type;
pub use extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
pub use extrinsic_transaction_extensions::ExtrinsicTransactionParams;
pub use extrinsics_type::{Extrinsic, Extrinsics};
/// Work with extrinsics.
+2 -2
View File
@@ -98,7 +98,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
}
/// Attempt to decode the fields into the given type.
pub fn decode<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
pub fn decode_as<T: scale_decode::DecodeAsFields>(&self) -> Result<T, ExtrinsicCallError> {
with_info!(&self.info => {
let cursor = &mut self.bytes();
let mut fields = &mut info.info.call_data().map(|named_arg| {
@@ -156,7 +156,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallField<'extrinsics, 'atblock> {
}
/// Attempt to decode the value of this field into the given type.
pub fn decode<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<T, ExtrinsicCallError> {
with_call_field_info!(&self.info => {
let cursor = &mut &*self.field_bytes;
let decoded = T::decode_as_type(cursor, info.info.ty().clone(), info.resolver)
@@ -16,7 +16,7 @@ struct ExtrinsicExtensionsInfo<'extrinsics, 'atblock, TypeId, Resolver> {
}
/// This represents the transaction extensions of an extrinsic.
pub struct ExtrinsicTransactionExtensions<'extrinsics, 'atblock> {
pub struct ExtrinsicTransactionParams<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock>,
}
@@ -31,7 +31,7 @@ macro_rules! with_extensions_info {
};
}
impl<'extrinsics, 'atblock> ExtrinsicTransactionExtensions<'extrinsics, 'atblock> {
impl<'extrinsics, 'atblock> ExtrinsicTransactionParams<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
@@ -105,7 +105,7 @@ impl<'extrinsics, 'atblock> ExtrinsicTransactionExtensions<'extrinsics, 'atblock
/// Attempt to decode the transaction extensions into a type where each field name is the name of the transaction
/// extension and the field value is the decoded extension.
pub fn decode<T: scale_decode::DecodeAsFields>(
pub fn decode_as<T: scale_decode::DecodeAsFields>(
&self,
) -> Result<T, ExtrinsicTransactionExtensionError> {
with_extensions_info!(&self.info => {
@@ -189,7 +189,7 @@ impl<'extrinsics, 'atblock> ExtrinsicTransactionExtension<'extrinsics, 'atblock>
}
/// Decode the bytes for this transaction extension into a type that implements `scale_decode::DecodeAsType`.
pub fn decode<T: scale_decode::DecodeAsType>(
pub fn decode_as<T: scale_decode::DecodeAsType>(
&self,
) -> Result<T, ExtrinsicTransactionExtensionError> {
with_extension_info!(&self.info => {
+3 -3
View File
@@ -1,6 +1,6 @@
use super::extrinsic_call::ExtrinsicCall;
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
use super::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use super::extrinsic_transaction_extensions::ExtrinsicTransactionParams;
use crate::client::OfflineClientAtBlockT;
use crate::config::Config;
use crate::error::ExtrinsicsError;
@@ -106,8 +106,8 @@ impl<'extrinsics, 'atblock> Extrinsic<'extrinsics, 'atblock> {
/// Get information about the transaction extensions of this extrinsic.
pub fn transaction_extensions(
&self,
) -> Option<ExtrinsicTransactionExtensions<'extrinsics, 'atblock>> {
ExtrinsicTransactionExtensions::new(self.bytes, self.info)
) -> Option<ExtrinsicTransactionParams<'extrinsics, 'atblock>> {
ExtrinsicTransactionParams::new(self.bytes, self.info)
}
}
+94 -219
View File
@@ -5,16 +5,17 @@ mod storage_value;
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
use crate::config::Config;
use crate::error::{StorageEntryIsNotAMap, StorageEntryIsNotAPlainValue, StorageError};
use crate::error::StorageError;
use crate::storage::storage_info::with_info;
use std::borrow::Cow;
use std::sync::Arc;
use storage_info::AnyStorageInfo;
pub use storage_entry::StorageEntry;
pub use storage_key::{StorageHasher, StorageKey, StorageKeyPart};
pub use storage_value::StorageValue;
// We take how storage keys can be passed in from `frame-decode`, so re-export here.
pub use frame_decode::storage::{IntoStorageKeys, StorageKeys};
pub use frame_decode::storage::{EncodableValues, IntoEncodableValues};
/// Work with storage.
pub struct StorageClient<'atblock, Client, T> {
@@ -33,44 +34,34 @@ impl<'atblock, Client, T> StorageClient<'atblock, Client, T> {
}
// Things that we can do offline with storage.
impl<'atblock, 'client: 'atblock, Client, T> StorageClient<'atblock, Client, T>
impl<'atblock, Client, T> StorageClient<'atblock, Client, T>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// Select the storage entry you'd like to work with.
pub fn entry(
&self,
pallet_name: impl Into<String>,
storage_name: impl Into<String>,
entry_name: impl Into<String>,
) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
let pallet_name = pallet_name.into();
let storage_name = storage_name.into();
let entry_name = entry_name.into();
let storage_info = AnyStorageInfo::new(
&pallet_name,
&storage_name,
&entry_name,
self.client.metadata(),
self.client.legacy_types(),
)?;
if storage_info.is_map() {
Ok(StorageEntryClient::Map(StorageEntryMapClient {
client: self.client,
pallet_name,
storage_name,
info: storage_info,
marker: std::marker::PhantomData,
}))
} else {
Ok(StorageEntryClient::Plain(StorageEntryPlainClient {
client: self.client,
pallet_name,
storage_name,
info: storage_info,
marker: std::marker::PhantomData,
}))
}
Ok(StorageEntryClient {
client: self.client,
pallet_name,
entry_name,
info: Arc::new(storage_info),
marker: std::marker::PhantomData,
})
}
/// Iterate over all of the storage entries listed in the metadata for the current block. This does **not** include well known
@@ -78,6 +69,7 @@ where
pub fn entries(&self) -> impl Iterator<Item = StorageEntriesItem<'atblock, Client, T>> {
let client = self.client;
let metadata = client.metadata();
frame_decode::helpers::list_storage_entries_any(metadata).map(|entry| StorageEntriesItem {
entry,
client: self.client,
@@ -88,24 +80,24 @@ where
/// Working with a specific storage entry.
pub struct StorageEntriesItem<'atblock, Client, T> {
entry: frame_decode::helpers::StorageEntry<'atblock>,
entry: frame_decode::storage::StorageEntry<'atblock>,
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, 'client: 'atblock, Client, T> StorageEntriesItem<'atblock, Client, T>
impl<'atblock, Client, T> StorageEntriesItem<'atblock, Client, T>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// The pallet name.
pub fn pallet_name(&self) -> &str {
self.entry.pallet()
&self.entry.pallet_name
}
/// The storage entry name.
pub fn storage_name(&self) -> &str {
self.entry.entry()
pub fn entry_name(&self) -> &str {
&self.entry.storage_entry
}
/// Extract the relevant storage information so that we can work with this entry.
@@ -115,87 +107,22 @@ where
marker: std::marker::PhantomData,
}
.entry(
self.entry.pallet().to_owned(),
self.entry.entry().to_owned(),
self.entry.pallet_name.clone(),
self.entry.storage_entry.clone(),
)
}
}
/// A client for working with a specific storage entry. This is an enum because the storage entry
/// might be either a map or a plain value, and each has a different interface.
pub enum StorageEntryClient<'atblock, Client, T> {
Plain(StorageEntryPlainClient<'atblock, Client, T>),
Map(StorageEntryMapClient<'atblock, Client, T>),
/// A client for working with a specific storage entry.
pub struct StorageEntryClient<'atblock, Client, T> {
client: &'atblock Client,
pallet_name: String,
entry_name: String,
info: Arc<AnyStorageInfo<'atblock>>,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// Get the pallet name.
pub fn pallet_name(&self) -> &str {
match self {
StorageEntryClient::Plain(client) => &client.pallet_name,
StorageEntryClient::Map(client) => &client.pallet_name,
}
}
/// Get the storage entry name.
pub fn storage_name(&self) -> &str {
match self {
StorageEntryClient::Plain(client) => &client.storage_name,
StorageEntryClient::Map(client) => &client.storage_name,
}
}
/// Is the storage entry a plain value?
pub fn is_plain(&self) -> bool {
matches!(self, StorageEntryClient::Plain(_))
}
/// Is the storage entry a map?
pub fn is_map(&self) -> bool {
matches!(self, StorageEntryClient::Map(_))
}
/// If this storage entry is a plain value, return the client for working with it. Else return an error.
pub fn into_plain(
self,
) -> Result<StorageEntryPlainClient<'atblock, Client, T>, StorageEntryIsNotAPlainValue> {
match self {
StorageEntryClient::Plain(client) => Ok(client),
StorageEntryClient::Map(_) => Err(StorageEntryIsNotAPlainValue {
pallet_name: self.pallet_name().into(),
storage_name: self.storage_name().into(),
}),
}
}
/// If this storage entry is a map, return the client for working with it. Else return an error.
pub fn into_map(
self,
) -> Result<StorageEntryMapClient<'atblock, Client, T>, StorageEntryIsNotAMap> {
match self {
StorageEntryClient::Plain(_) => Err(StorageEntryIsNotAMap {
pallet_name: self.pallet_name().into(),
storage_name: self.storage_name().into(),
}),
StorageEntryClient::Map(client) => Ok(client),
}
}
}
/// A client for working with a plain storage entry.
pub struct StorageEntryPlainClient<'atblock, Client, T> {
client: &'atblock Client,
pallet_name: String,
storage_name: String,
info: AnyStorageInfo<'atblock>,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageEntryPlainClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
@@ -206,134 +133,72 @@ where
}
/// Get the storage entry name.
pub fn storage_name(&self) -> &str {
&self.storage_name
pub fn entry_name(&self) -> &str {
&self.entry_name
}
/// Return the default value for this storage entry, if there is one. Returns `None` if there
/// is no default value.
pub fn default(&self) -> Option<StorageValue<'_, 'atblock>> {
with_info!(info = &self.info => {
info.info.default_value.as_ref().map(|default_value| {
StorageValue::new(&self.info, default_value.clone())
})
})
}
}
impl<'atblock, Client, T> StorageEntryPlainClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OnlineClientAtBlockT<'atblock, T>,
{
/// Fetch the value for this storage entry.
pub async fn fetch(&self) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
let key_bytes = self.key();
fetch(self.client, &key_bytes)
.await
.map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))))
}
/// Fetch the value for this storage entry as per [`StorageEntryPlainClient::fetch`], but return the default
/// value for the storage entry if one exists and the entry does not exist.
pub async fn fetch_or_default(
&self,
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
self.fetch()
.await
.map(|option_val| option_val.or_else(|| self.default()))
}
/// The key for this storage entry.
pub fn key(&self) -> [u8; 32] {
/// The key which points to this storage entry (but not necessarily any values within it).
pub fn key_prefix(&self) -> [u8; 32] {
let pallet_name = &*self.pallet_name;
let storage_name = &*self.storage_name;
let entry_name = &*self.entry_name;
frame_decode::storage::encode_prefix(pallet_name, storage_name)
}
}
/// A client for working with a storage entry that is a map.
pub struct StorageEntryMapClient<'atblock, Client, T> {
client: &'atblock Client,
pallet_name: String,
storage_name: String,
info: AnyStorageInfo<'atblock>,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageEntryMapClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// Get the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// Get the storage entry name.
pub fn storage_name(&self) -> &str {
&self.storage_name
frame_decode::storage::encode_storage_key_prefix(pallet_name, entry_name)
}
/// Return the default value for this storage entry, if there is one. Returns `None` if there
/// is no default value.
pub fn default(&self) -> Option<StorageValue<'_, 'atblock>> {
with_info!(info = &self.info => {
pub fn default_value(&self) -> Option<StorageValue<'atblock>> {
with_info!(info = &*self.info => {
info.info.default_value.as_ref().map(|default_value| {
StorageValue::new(&self.info, default_value.clone())
StorageValue::new(self.info.clone(), default_value.clone())
})
})
}
}
impl<'atblock, Client, T> StorageEntryMapClient<'atblock, Client, T>
impl<'atblock, Client, T> StorageEntryClient<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OnlineClientAtBlockT<'atblock, T>,
{
/// Fetch a specific key in this map. If the number of keys provided is not equal
/// to the number of keys required to fetch a single value from the map, then an error
/// will be emitted.
pub async fn fetch<Keys: IntoStorageKeys>(
/// will be emitted. If no value exists but there is a default value for this storage
/// entry, then the default value will be returned. Else, `None` will be returned.
pub async fn fetch<Keys: IntoEncodableValues>(
&self,
keys: Keys,
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
let expected_num_keys = with_info!(info = &self.info => {
) -> Result<Option<StorageValue<'atblock>>, StorageError> {
let expected_num_keys = with_info!(info = &*self.info => {
info.info.keys.len()
});
if expected_num_keys != keys.num_keys() {
return Err(StorageError::WrongNumberOfKeysProvided {
num_keys_provided: keys.num_keys(),
// For fetching, we need exactly as many keys as exist for a storage entry.
if expected_num_keys != keys.num_encodable_values() {
return Err(StorageError::WrongNumberOfKeysProvidedForFetch {
num_keys_provided: keys.num_encodable_values(),
num_keys_expected: expected_num_keys,
});
}
let key_bytes = self.key(keys)?;
fetch(self.client, &key_bytes)
.await
.map(|v| v.map(|bytes| StorageValue::new(&self.info, Cow::Owned(bytes))))
}
let info = self.info.clone();
let value = fetch(self.client, &key_bytes)
.await?
.map(|bytes| StorageValue::new(info, Cow::Owned(bytes)))
.or_else(|| self.default_value());
/// Fetch a specific key in this map as per [`StorageEntryMapClient::fetch`], but return the default
/// value for the storage entry if one exists and the entry was not found.
pub async fn fetch_or_default<Keys: IntoStorageKeys>(
&self,
keys: Keys,
) -> Result<Option<StorageValue<'_, 'atblock>>, StorageError> {
self.fetch(keys)
.await
.map(|option_val| option_val.or_else(|| self.default()))
Ok(value)
}
/// Iterate over the values underneath the provided keys.
pub async fn iter<Keys: IntoStorageKeys>(
pub async fn iter<Keys: IntoEncodableValues>(
&self,
keys: Keys,
) -> Result<
impl futures::Stream<Item = Result<StorageEntry<'_, 'atblock>, StorageError>> + Unpin,
impl futures::Stream<Item = Result<StorageEntry<'atblock>, StorageError>>
+ Unpin
+ use<'atblock, Client, T, Keys>,
StorageError,
> {
use futures::stream::StreamExt;
@@ -341,6 +206,19 @@ where
ArchiveStorageEvent, StorageQuery, StorageQueryType,
};
let expected_num_keys = with_info!(info = &*self.info => {
info.info.keys.len()
});
// For iterating, we need at most one less key than the number that exists for a storage entry.
// TODO: The error message will be confusing if == keys are provided!
if keys.num_encodable_values() >= expected_num_keys {
return Err(StorageError::TooManyKeysProvidedForIter {
num_keys_provided: keys.num_encodable_values(),
max_keys_expected: expected_num_keys - 1,
});
}
let block_hash = self.client.block_hash();
let key_bytes = self.key(keys)?;
@@ -356,23 +234,22 @@ where
.await
.map_err(|e| StorageError::RpcError { reason: e })?;
let sub = sub.filter_map(async |item| {
let item = match item {
Ok(ArchiveStorageEvent::Item(item)) => item,
Ok(ArchiveStorageEvent::Error(err)) => {
return Some(Err(StorageError::StorageEventError { reason: err.error }));
}
Ok(ArchiveStorageEvent::Done) => return None,
Err(e) => return Some(Err(StorageError::RpcError { reason: e })),
};
let info = self.info.clone();
let sub = sub.filter_map(move |item| {
let info = info.clone();
async move {
let item = match item {
Ok(ArchiveStorageEvent::Item(item)) => item,
Ok(ArchiveStorageEvent::Error(err)) => {
return Some(Err(StorageError::StorageEventError { reason: err.error }));
}
Ok(ArchiveStorageEvent::Done) => return None,
Err(e) => return Some(Err(StorageError::RpcError { reason: e })),
};
item.value.map(|value| {
Ok(StorageEntry::new(
&self.info,
item.key.0,
Cow::Owned(value.0),
))
})
item.value
.map(|value| Ok(StorageEntry::new(info, item.key.0, Cow::Owned(value.0))))
}
});
Ok(Box::pin(sub))
@@ -384,16 +261,14 @@ where
// Dev note: We don't have any functions that can take an already-encoded key and fetch an entry from
// it yet, so we don't expose this. If we did expose it, we might want to return some struct that wraps
// the key bytes and some metadata about them. Or maybe just fetch_raw and iter_raw.
fn key<Keys: IntoStorageKeys>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
with_info!(info = &self.info => {
let mut key_bytes = Vec::new();
frame_decode::storage::encode_storage_key_with_info_to(
fn key<Keys: IntoEncodableValues>(&self, keys: Keys) -> Result<Vec<u8>, StorageError> {
with_info!(info = &*self.info => {
let key_bytes = frame_decode::storage::encode_storage_key_with_info(
&self.pallet_name,
&self.storage_name,
&self.entry_name,
keys,
&info.info,
info.resolver,
&mut key_bytes,
).map_err(|e| StorageError::KeyEncodeError { reason: e })?;
Ok(key_bytes)
})
+11 -16
View File
@@ -1,21 +1,21 @@
use super::storage_info::AnyStorageInfo;
use super::storage_key::StorageKey;
use super::storage_value::StorageValue;
use crate::error::{StorageKeyError, StorageValueError};
use scale_decode::DecodeAsType;
use crate::error::StorageKeyError;
use std::borrow::Cow;
use std::sync::Arc;
/// This represents a storage entry, which is a key-value pair in the storage.
pub struct StorageEntry<'entry, 'atblock> {
pub struct StorageEntry<'atblock> {
key: Vec<u8>,
// This contains the storage information already:
value: StorageValue<'entry, 'atblock>,
value: StorageValue<'atblock>,
}
impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> {
impl<'atblock> StorageEntry<'atblock> {
/// Create a new storage entry.
pub fn new(
info: &'entry AnyStorageInfo<'atblock>,
info: Arc<AnyStorageInfo<'atblock>>,
key: Vec<u8>,
value: Cow<'atblock, [u8]>,
) -> Self {
@@ -30,11 +30,6 @@ impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> {
&self.key
}
/// Get the raw bytes for this storage entry's value.
pub fn value_bytes(&self) -> &[u8] {
self.value.bytes()
}
/// Consume this storage entry and return the raw bytes for the key and value.
pub fn into_key_and_value_bytes(self) -> (Vec<u8>, Vec<u8>) {
(self.key, self.value.into_bytes())
@@ -42,12 +37,12 @@ impl<'entry, 'atblock> StorageEntry<'entry, 'atblock> {
/// Decode the key for this storage entry. This gives back a type from which we can
/// decode specific parts of the key hash (where applicable).
pub fn decode_key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
StorageKey::new(self.value.info, &self.key)
pub fn key(&'_ self) -> Result<StorageKey<'_, 'atblock>, StorageKeyError> {
StorageKey::new(&self.value.info, &self.key)
}
/// Decode this storage value.
pub fn decode_value<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
self.value.decode::<T>()
/// Return the storage value.
pub fn value(&self) -> &StorageValue<'atblock> {
&self.value
}
}
+2 -2
View File
@@ -44,7 +44,7 @@ impl<'atblock> AnyStorageInfo<'atblock> {
Resolver: scale_type_resolver::TypeResolver<TypeId = Info::TypeId>,
AnyStorageInfo<'atblock>: From<StorageInfo<'atblock, Info::TypeId, Resolver>>,
{
m.get_storage_info(pallet_name, entry_name)
m.storage_info(pallet_name, entry_name)
.map(|frame_storage_info| {
let info = StorageInfo {
info: frame_storage_info,
@@ -84,7 +84,7 @@ impl<'atblock> From<StorageInfo<'atblock, u32, scale_info::PortableRegistry>>
}
}
pub struct StorageInfo<'atblock, TypeId, Resolver> {
pub struct StorageInfo<'atblock, TypeId: Clone, Resolver> {
pub info: frame_decode::storage::StorageInfo<'atblock, TypeId>,
pub resolver: &'atblock Resolver,
}
+19 -2
View File
@@ -3,7 +3,7 @@ use crate::{error::StorageKeyError, storage::storage_info::with_info};
use scale_info_legacy::{LookupName, TypeRegistrySet};
// This is part of our public interface.
pub use frame_decode::storage::StorageHasher;
pub use frame_decode::storage::{IntoDecodableValues, StorageHasher};
enum AnyStorageKeyInfo<'atblock> {
Legacy(StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
@@ -78,6 +78,23 @@ impl<'entry, 'atblock> StorageKey<'entry, 'atblock> {
})
}
/// Attempt to decode the values contained within this storage key to the `Target` type
/// provided. This type is typically a tuple of types which each implement [`scale_decode::DecodeAsType`]
/// and correspond to each of the key types present, in order.
pub fn decode_as<Target: IntoDecodableValues>(&self) -> Result<Target, StorageKeyError> {
with_key_info!(info = &self.info => {
let values = frame_decode::storage::decode_storage_key_values(
self.bytes,
&info.info,
info.resolver
).map_err(|e| {
StorageKeyError::DecodeKeyValueError { reason: e }
})?;
Ok(values)
})
}
/// Iterate over the parts of this storage key. Each part of a storage key corresponds to a
/// single value that has been hashed.
pub fn parts(&'_ self) -> impl ExactSizeIterator<Item = StorageKeyPart<'_, 'entry, 'atblock>> {
@@ -137,7 +154,7 @@ impl<'key, 'entry, 'atblock> StorageKeyPart<'key, 'entry, 'atblock> {
/// is available as a part of the key hash, allowing us to decode it into anything
/// implementing [`scale_decode::DecodeAsType`]. If the key was produced using a
/// different hasher, this will return `None`.
pub fn decode<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
pub fn decode_as<T: scale_decode::DecodeAsType>(&self) -> Result<Option<T>, StorageKeyError> {
with_key_info!(info = &self.info => {
let part_info = &info.info[self.index];
let Some(value_info) = part_info.value() else {
+7 -6
View File
@@ -3,16 +3,17 @@ use super::storage_info::with_info;
use crate::error::StorageValueError;
use scale_decode::DecodeAsType;
use std::borrow::Cow;
use std::sync::Arc;
/// This represents a storage value.
pub struct StorageValue<'entry, 'atblock> {
pub(crate) info: &'entry AnyStorageInfo<'atblock>,
pub struct StorageValue<'atblock> {
pub(crate) info: Arc<AnyStorageInfo<'atblock>>,
bytes: Cow<'atblock, [u8]>,
}
impl<'entry, 'atblock> StorageValue<'entry, 'atblock> {
impl<'atblock> StorageValue<'atblock> {
/// Create a new storage value.
pub fn new(info: &'entry AnyStorageInfo<'atblock>, bytes: Cow<'atblock, [u8]>) -> Self {
pub fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
Self { info, bytes }
}
@@ -27,8 +28,8 @@ impl<'entry, 'atblock> StorageValue<'entry, 'atblock> {
}
/// Decode this storage value.
pub fn decode<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
with_info!(info = &self.info => {
pub fn decode_as<T: DecodeAsType>(&self) -> Result<T, StorageValueError> {
with_info!(info = &*self.info => {
let cursor = &mut &*self.bytes;
let value = T::decode_as_type(
+3 -5
View File
@@ -74,11 +74,9 @@ fn bench_get_storage_hash(c: &mut Criterion) {
};
for storage in storage_entries.entries() {
let storage_name = storage.name();
let bench_name = format!("{pallet_name}/{storage_name}");
group.bench_function(&bench_name, |b| {
b.iter(|| pallet.storage_hash(storage_name))
});
let entry_name = storage.name();
let bench_name = format!("{pallet_name}/{entry_name}");
group.bench_function(&bench_name, |b| b.iter(|| pallet.storage_hash(entry_name)));
}
}
}
+9
View File
@@ -33,6 +33,15 @@ pub enum TryFromError {
/// Invalid type path.
#[error("Type has an invalid path {0}")]
InvalidTypePath(String),
/// Cannot decode storage entry information.
#[error("Error decoding storage entry information: {0}")]
StorageInfoError(#[from] frame_decode::storage::StorageInfoError<'static>),
/// Cannot decode Runtime API information.
#[error("Error decoding Runtime API information: {0}")]
RuntimeInfoError(#[from] frame_decode::runtime_apis::RuntimeApiInfoError<'static>),
/// Cannot decode View Function information.
#[error("Error decoding View Function information: {0}")]
ViewFunctionInfoError(#[from] frame_decode::view_functions::ViewFunctionInfoError<'static>),
}
impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for crate::Metadata {
+39 -75
View File
@@ -6,9 +6,8 @@ use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
ArcStr, ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
PalletMetadataInner, StorageEntryMetadata, StorageEntryModifier, StorageEntryType,
StorageHasher, StorageMetadata, TransactionExtensionMetadataInner,
ConstantMetadata, CustomMetadataInner, ExtrinsicMetadata, Metadata, OuterEnumsMetadata,
PalletMetadataInner, StorageEntryMetadata, StorageMetadata, TransactionExtensionMetadataInner,
utils::ordered_map::OrderedMap,
};
use alloc::borrow::ToOwned;
@@ -16,6 +15,7 @@ use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::{format, vec};
use frame_decode::storage::StorageTypeInfo;
use frame_metadata::v14;
use hashbrown::HashMap;
use scale_info::form::PortableForm;
@@ -28,23 +28,37 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.into_iter().enumerate() {
let name: ArcStr = p.name.into();
for (pos, p) in m.pallets.iter().enumerate() {
let name: String = p.name.clone();
let storage = p.storage.map(|s| StorageMetadata {
prefix: s.prefix,
entries: s
.entries
.into_iter()
.map(|s| {
let name: ArcStr = s.name.clone().into();
(name.clone(), from_storage_entry_metadata(name, s))
})
.collect(),
});
let constants = p.constants.into_iter().map(|c| {
let name: ArcStr = c.name.clone().into();
(name.clone(), from_constant_metadata(name, c))
let storage = match &p.storage {
None => None,
Some(s) => Some(StorageMetadata {
prefix: s.prefix.clone(),
entries: s
.entries
.iter()
.map(|s| {
let entry_name: String = s.name.clone();
let storage_info = m
.storage_info(&name, &entry_name)
.map_err(|e| e.into_owned())?
.into_owned();
let storage_entry = StorageEntryMetadata {
name: entry_name.clone(),
info: storage_info,
docs: s.docs.clone(),
};
Ok::<_, TryFromError>((entry_name, storage_entry))
})
.collect::<Result<_, TryFromError>>()?,
}),
};
let constants = p.constants.iter().map(|c| {
let name = c.name.clone();
(name, from_constant_metadata(c.clone()))
});
let call_variant_index =
@@ -58,14 +72,14 @@ impl TryFrom<v14::RuntimeMetadataV14> for Metadata {
pallets.push_insert(
name.clone(),
PalletMetadataInner {
name,
name: name.clone(),
index: p.index,
storage,
call_ty: p.calls.map(|c| c.ty.id),
call_ty: p.calls.as_ref().map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.map(|e| e.ty.id),
event_ty: p.event.as_ref().map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.map(|e| e.ty.id),
error_ty: p.error.as_ref().map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: Default::default(),
@@ -135,59 +149,9 @@ fn from_extrinsic_metadata(
}
}
fn from_storage_hasher(value: v14::StorageHasher) -> StorageHasher {
match value {
v14::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
v14::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
v14::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
v14::StorageHasher::Twox128 => StorageHasher::Twox128,
v14::StorageHasher::Twox256 => StorageHasher::Twox256,
v14::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
v14::StorageHasher::Identity => StorageHasher::Identity,
}
}
fn from_storage_entry_type(value: v14::StorageEntryType<PortableForm>) -> StorageEntryType {
match value {
v14::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
v14::StorageEntryType::Map {
hashers,
key,
value,
} => StorageEntryType::Map {
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
key_ty: key.id,
value_ty: value.id,
},
}
}
fn from_storage_entry_modifier(value: v14::StorageEntryModifier) -> StorageEntryModifier {
match value {
v14::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
v14::StorageEntryModifier::Default => StorageEntryModifier::Default,
}
}
fn from_storage_entry_metadata(
name: ArcStr,
s: v14::StorageEntryMetadata<PortableForm>,
) -> StorageEntryMetadata {
StorageEntryMetadata {
name,
modifier: from_storage_entry_modifier(s.modifier),
entry_type: from_storage_entry_type(s.ty),
default: s.default,
docs: s.docs,
}
}
fn from_constant_metadata(
name: ArcStr,
s: v14::PalletConstantMetadata<PortableForm>,
) -> ConstantMetadata {
fn from_constant_metadata(s: v14::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
ConstantMetadata {
name,
name: s.name,
ty: s.ty.id,
value: s.value,
docs: s.docs,
+71 -123
View File
@@ -6,14 +6,15 @@ use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, MethodParamMetadata, OuterEnumsMetadata,
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner,
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
TransactionExtensionMetadataInner, utils::ordered_map::OrderedMap,
};
use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
use frame_decode::storage::StorageTypeInfo;
use frame_metadata::v15;
use hashbrown::HashMap;
use scale_info::form::PortableForm;
@@ -23,23 +24,37 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
fn try_from(m: v15::RuntimeMetadataV15) -> Result<Self, TryFromError> {
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.into_iter().enumerate() {
let name: ArcStr = p.name.into();
for (pos, p) in m.pallets.iter().enumerate() {
let name = p.name.clone();
let storage = p.storage.map(|s| StorageMetadata {
prefix: s.prefix,
entries: s
.entries
.into_iter()
.map(|s| {
let name: ArcStr = s.name.clone().into();
(name.clone(), from_storage_entry_metadata(name, s))
})
.collect(),
});
let constants = p.constants.into_iter().map(|c| {
let name: ArcStr = c.name.clone().into();
(name.clone(), from_constant_metadata(name, c))
let storage = match &p.storage {
None => None,
Some(s) => Some(StorageMetadata {
prefix: s.prefix.clone(),
entries: s
.entries
.iter()
.map(|s| {
let entry_name = s.name.clone();
let storage_info = m
.storage_info(&name, &entry_name)
.map_err(|e| e.into_owned())?
.into_owned();
let storage_entry = StorageEntryMetadata {
name: entry_name.clone(),
info: storage_info,
docs: s.docs.clone(),
};
Ok::<_, TryFromError>((entry_name, storage_entry))
})
.collect::<Result<_, TryFromError>>()?,
}),
};
let constants = p.constants.iter().map(|c| {
let name = c.name.clone();
(name, from_constant_metadata(c.clone()))
});
let call_variant_index =
@@ -56,24 +71,50 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
name,
index: p.index,
storage,
call_ty: p.calls.map(|c| c.ty.id),
call_ty: p.calls.as_ref().map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.map(|e| e.ty.id),
event_ty: p.event.as_ref().map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.map(|e| e.ty.id),
error_ty: p.error.as_ref().map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: Default::default(),
associated_types: Default::default(),
docs: p.docs,
docs: p.docs.clone(),
},
);
}
let apis = m.apis.into_iter().map(|api| {
let name: ArcStr = api.name.clone().into();
(name.clone(), from_runtime_api_metadata(name, api))
});
let apis = m
.apis
.iter()
.map(|api| {
let trait_name = api.name.clone();
let methods = api
.methods
.iter()
.map(|method| {
let method_name = method.name.clone();
let method_info = RuntimeApiMethodMetadataInner {
info: m
.runtime_api_info(&trait_name, &method.name)
.map_err(|e| e.into_owned())?
.into_owned(),
name: method.name.clone(),
docs: method.docs.clone(),
};
Ok((method_name, method_info))
})
.collect::<Result<_, TryFromError>>()?;
let runtime_api_metadata = RuntimeApiMetadataInner {
name: trait_name.clone(),
methods,
docs: api.docs.clone(),
};
Ok((trait_name, runtime_api_metadata))
})
.collect::<Result<_, TryFromError>>()?;
let dispatch_error_ty = m
.types
@@ -88,7 +129,7 @@ impl TryFrom<v15::RuntimeMetadataV15> for Metadata {
pallets_by_index,
extrinsic: from_extrinsic_metadata(m.extrinsic),
dispatch_error_ty,
apis: apis.collect(),
apis,
outer_enums: OuterEnumsMetadata {
call_enum_ty: m.outer_enums.call_enum_ty.id,
event_enum_ty: m.outer_enums.event_enum_ty.id,
@@ -130,104 +171,11 @@ fn from_extrinsic_metadata(value: v15::ExtrinsicMetadata<PortableForm>) -> Extri
}
}
fn from_storage_hasher(value: v15::StorageHasher) -> StorageHasher {
match value {
v15::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
v15::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
v15::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
v15::StorageHasher::Twox128 => StorageHasher::Twox128,
v15::StorageHasher::Twox256 => StorageHasher::Twox256,
v15::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
v15::StorageHasher::Identity => StorageHasher::Identity,
}
}
fn from_storage_entry_type(value: v15::StorageEntryType<PortableForm>) -> StorageEntryType {
match value {
v15::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
v15::StorageEntryType::Map {
hashers,
key,
value,
} => StorageEntryType::Map {
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
key_ty: key.id,
value_ty: value.id,
},
}
}
fn from_storage_entry_modifier(value: v15::StorageEntryModifier) -> StorageEntryModifier {
match value {
v15::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
v15::StorageEntryModifier::Default => StorageEntryModifier::Default,
}
}
fn from_storage_entry_metadata(
name: ArcStr,
s: v15::StorageEntryMetadata<PortableForm>,
) -> StorageEntryMetadata {
StorageEntryMetadata {
name,
modifier: from_storage_entry_modifier(s.modifier),
entry_type: from_storage_entry_type(s.ty),
default: s.default,
docs: s.docs,
}
}
fn from_constant_metadata(
name: ArcStr,
s: v15::PalletConstantMetadata<PortableForm>,
) -> ConstantMetadata {
fn from_constant_metadata(s: v15::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
ConstantMetadata {
name,
name: s.name,
ty: s.ty.id,
value: s.value,
docs: s.docs,
}
}
fn from_runtime_api_metadata(
name: ArcStr,
s: v15::RuntimeApiMetadata<PortableForm>,
) -> RuntimeApiMetadataInner {
RuntimeApiMetadataInner {
name,
docs: s.docs,
methods: s
.methods
.into_iter()
.map(|m| {
let name: ArcStr = m.name.clone().into();
(name.clone(), from_runtime_api_method_metadata(name, m))
})
.collect(),
}
}
fn from_runtime_api_method_metadata(
name: ArcStr,
s: v15::RuntimeApiMethodMetadata<PortableForm>,
) -> RuntimeApiMethodMetadataInner {
RuntimeApiMethodMetadataInner {
name,
inputs: s
.inputs
.into_iter()
.map(from_runtime_api_method_param_metadata)
.collect(),
output_ty: s.output.id,
docs: s.docs,
}
}
fn from_runtime_api_method_param_metadata(
s: v15::RuntimeApiMethodParamMetadata<PortableForm>,
) -> MethodParamMetadata {
MethodParamMetadata {
name: s.name,
ty: s.ty.id,
}
}
+96 -151
View File
@@ -6,11 +6,13 @@ use super::TryFromError;
use crate::utils::variant_index::VariantIndex;
use crate::{
ArcStr, ConstantMetadata, ExtrinsicMetadata, Metadata, MethodParamMetadata, OuterEnumsMetadata,
PalletMetadataInner, RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner,
StorageEntryMetadata, StorageEntryModifier, StorageEntryType, StorageHasher, StorageMetadata,
ConstantMetadata, ExtrinsicMetadata, Metadata, OuterEnumsMetadata, PalletMetadataInner,
RuntimeApiMetadataInner, RuntimeApiMethodMetadataInner, StorageEntryMetadata, StorageMetadata,
TransactionExtensionMetadataInner, ViewFunctionMetadataInner, utils::ordered_map::OrderedMap,
};
use frame_decode::runtime_apis::RuntimeApiTypeInfo;
use frame_decode::storage::StorageTypeInfo;
use frame_decode::view_functions::ViewFunctionTypeInfo;
use frame_metadata::{v15, v16};
use hashbrown::HashMap;
use scale_info::form::PortableForm;
@@ -18,43 +20,67 @@ use scale_info::form::PortableForm;
impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
type Error = TryFromError;
fn try_from(m: v16::RuntimeMetadataV16) -> Result<Self, TryFromError> {
let types = m.types;
let types = &m.types;
let mut pallets = OrderedMap::new();
let mut pallets_by_index = HashMap::new();
for (pos, p) in m.pallets.into_iter().enumerate() {
let name: ArcStr = p.name.into();
for (pos, p) in m.pallets.iter().enumerate() {
let name = p.name.clone();
let storage = p.storage.map(|s| StorageMetadata {
prefix: s.prefix,
entries: s
.entries
.into_iter()
.map(|s| {
let name: ArcStr = s.name.clone().into();
(name.clone(), from_storage_entry_metadata(name, s))
})
.collect(),
});
let constants = p.constants.into_iter().map(|c| {
let name: ArcStr = c.name.clone().into();
(name.clone(), from_constant_metadata(name, c))
});
let view_functions = p.view_functions.into_iter().map(|v| {
let name: ArcStr = v.name.clone().into();
(name.clone(), from_view_function_metadata(name, v))
let storage = match &p.storage {
None => None,
Some(s) => Some(StorageMetadata {
prefix: s.prefix.clone(),
entries: s
.entries
.iter()
.map(|s| {
let entry_name = s.name.clone();
let storage_info = m
.storage_info(&name, &entry_name)
.map_err(|e| e.into_owned())?
.into_owned();
let storage_entry = StorageEntryMetadata {
name: entry_name.clone(),
info: storage_info,
docs: s.docs.clone(),
};
Ok::<_, TryFromError>((entry_name, storage_entry))
})
.collect::<Result<_, TryFromError>>()?,
}),
};
let view_functions = p
.view_functions
.iter()
.map(|vf| {
let view_function_metadata = ViewFunctionMetadataInner {
name: vf.name.clone(),
info: m
.view_function_info(&name, &vf.name)
.map_err(|e| e.into_owned())?
.into_owned(),
docs: vf.docs.clone(),
};
Ok((vf.name.clone(), view_function_metadata))
})
.collect::<Result<_, TryFromError>>()?;
let constants = p.constants.iter().map(|c| {
let name = c.name.clone();
(name, from_constant_metadata(c.clone()))
});
let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), &types);
let error_variant_index =
VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), &types);
let event_variant_index =
VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), &types);
let call_variant_index = VariantIndex::build(p.calls.as_ref().map(|c| c.ty.id), types);
let error_variant_index = VariantIndex::build(p.error.as_ref().map(|e| e.ty.id), types);
let event_variant_index = VariantIndex::build(p.event.as_ref().map(|e| e.ty.id), types);
let associated_types = p
.associated_types
.into_iter()
.map(|t| (t.name, t.ty.id))
.iter()
.map(|t| (t.name.clone(), t.ty.id))
.collect();
pallets_by_index.insert(p.index, pos);
@@ -64,24 +90,50 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
name,
index: p.index,
storage,
call_ty: p.calls.map(|c| c.ty.id),
call_ty: p.calls.as_ref().map(|c| c.ty.id),
call_variant_index,
event_ty: p.event.map(|e| e.ty.id),
event_ty: p.event.as_ref().map(|e| e.ty.id),
event_variant_index,
error_ty: p.error.map(|e| e.ty.id),
error_ty: p.error.as_ref().map(|e| e.ty.id),
error_variant_index,
constants: constants.collect(),
view_functions: view_functions.collect(),
view_functions,
associated_types,
docs: p.docs,
docs: p.docs.clone(),
},
);
}
let apis = m.apis.into_iter().map(|api| {
let name: ArcStr = api.name.clone().into();
(name.clone(), from_runtime_api_metadata(name, api))
});
let apis = m
.apis
.iter()
.map(|api| {
let trait_name = api.name.clone();
let methods = api
.methods
.iter()
.map(|method| {
let method_name = method.name.clone();
let method_info = RuntimeApiMethodMetadataInner {
info: m
.runtime_api_info(&trait_name, &method.name)
.map_err(|e| e.into_owned())?
.into_owned(),
name: method.name.clone(),
docs: method.docs.clone(),
};
Ok((method_name, method_info))
})
.collect::<Result<_, TryFromError>>()?;
let runtime_api_metadata = RuntimeApiMetadataInner {
name: trait_name.clone(),
methods,
docs: api.docs.clone(),
};
Ok((trait_name, runtime_api_metadata))
})
.collect::<Result<_, TryFromError>>()?;
let custom_map = m
.custom
@@ -103,12 +155,12 @@ impl TryFrom<v16::RuntimeMetadataV16> for Metadata {
.map(|ty| ty.id);
Ok(Metadata {
types,
types: m.types,
pallets,
pallets_by_index,
extrinsic: from_extrinsic_metadata(m.extrinsic),
dispatch_error_ty,
apis: apis.collect(),
apis,
outer_enums: OuterEnumsMetadata {
call_enum_ty: m.outer_enums.call_enum_ty.id,
event_enum_ty: m.outer_enums.event_enum_ty.id,
@@ -147,118 +199,11 @@ fn from_extrinsic_metadata(value: v16::ExtrinsicMetadata<PortableForm>) -> Extri
}
}
fn from_storage_hasher(value: v16::StorageHasher) -> StorageHasher {
match value {
v16::StorageHasher::Blake2_128 => StorageHasher::Blake2_128,
v16::StorageHasher::Blake2_256 => StorageHasher::Blake2_256,
v16::StorageHasher::Blake2_128Concat => StorageHasher::Blake2_128Concat,
v16::StorageHasher::Twox128 => StorageHasher::Twox128,
v16::StorageHasher::Twox256 => StorageHasher::Twox256,
v16::StorageHasher::Twox64Concat => StorageHasher::Twox64Concat,
v16::StorageHasher::Identity => StorageHasher::Identity,
}
}
fn from_storage_entry_type(value: v16::StorageEntryType<PortableForm>) -> StorageEntryType {
match value {
v16::StorageEntryType::Plain(ty) => StorageEntryType::Plain(ty.id),
v16::StorageEntryType::Map {
hashers,
key,
value,
} => StorageEntryType::Map {
hashers: hashers.into_iter().map(from_storage_hasher).collect(),
key_ty: key.id,
value_ty: value.id,
},
}
}
fn from_storage_entry_modifier(value: v16::StorageEntryModifier) -> StorageEntryModifier {
match value {
v16::StorageEntryModifier::Optional => StorageEntryModifier::Optional,
v16::StorageEntryModifier::Default => StorageEntryModifier::Default,
}
}
fn from_storage_entry_metadata(
name: ArcStr,
s: v16::StorageEntryMetadata<PortableForm>,
) -> StorageEntryMetadata {
StorageEntryMetadata {
name,
modifier: from_storage_entry_modifier(s.modifier),
entry_type: from_storage_entry_type(s.ty),
default: s.default,
docs: s.docs,
}
}
fn from_constant_metadata(
name: ArcStr,
s: v16::PalletConstantMetadata<PortableForm>,
) -> ConstantMetadata {
fn from_constant_metadata(s: v16::PalletConstantMetadata<PortableForm>) -> ConstantMetadata {
ConstantMetadata {
name,
name: s.name,
ty: s.ty.id,
value: s.value,
docs: s.docs,
}
}
fn from_runtime_api_metadata(
name: ArcStr,
s: v16::RuntimeApiMetadata<PortableForm>,
) -> RuntimeApiMetadataInner {
RuntimeApiMetadataInner {
name,
docs: s.docs,
methods: s
.methods
.into_iter()
.map(|m| {
let name: ArcStr = m.name.clone().into();
(name.clone(), from_runtime_api_method_metadata(name, m))
})
.collect(),
}
}
fn from_runtime_api_method_metadata(
name: ArcStr,
s: v16::RuntimeApiMethodMetadata<PortableForm>,
) -> RuntimeApiMethodMetadataInner {
RuntimeApiMethodMetadataInner {
name,
inputs: s
.inputs
.into_iter()
.map(|param| MethodParamMetadata {
name: param.name,
ty: param.ty.id,
})
.collect(),
output_ty: s.output.id,
docs: s.docs,
}
}
fn from_view_function_metadata(
name: ArcStr,
s: v16::PalletViewFunctionMetadata<PortableForm>,
) -> ViewFunctionMetadataInner {
ViewFunctionMetadataInner {
name,
query_id: s.id,
inputs: s
.inputs
.into_iter()
.map(|param| MethodParamMetadata {
name: param.name,
ty: param.ty.id,
})
.collect(),
output_ty: s.output.id,
docs: s.docs,
}
}
+251 -155
View File
@@ -24,13 +24,22 @@ mod utils;
use alloc::borrow::Cow;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use frame_decode::constants::{Constant, ConstantInfo, ConstantInfoError};
use frame_decode::custom_values::{CustomValue, CustomValueInfo, CustomValueInfoError};
use frame_decode::extrinsics::{
ExtrinsicCallInfo, ExtrinsicExtensionInfo, ExtrinsicInfoArg, ExtrinsicInfoError,
ExtrinsicSignatureInfo,
};
use frame_decode::runtime_apis::{
RuntimeApi, RuntimeApiInfo, RuntimeApiInfoError, RuntimeApiInput,
};
use frame_decode::storage::{StorageEntry, StorageInfo, StorageInfoError, StorageKeyInfo};
use frame_decode::view_functions::{
ViewFunction, ViewFunctionInfo, ViewFunctionInfoError, ViewFunctionInput,
};
use hashbrown::HashMap;
use scale_info::{PortableRegistry, Variant, form::PortableForm};
use utils::{
@@ -39,8 +48,7 @@ use utils::{
variant_index::VariantIndex,
};
type ArcStr = Arc<str>;
pub use frame_decode::storage::StorageHasher;
pub use from::SUPPORTED_METADATA_VERSIONS;
pub use from::TryFromError;
pub use utils::validation::MetadataHasher;
@@ -55,7 +63,7 @@ pub struct Metadata {
/// Type registry containing all types used in the metadata.
types: PortableRegistry,
/// Metadata of all the pallets.
pallets: OrderedMap<ArcStr, PalletMetadataInner>,
pallets: OrderedMap<String, PalletMetadataInner>,
/// Find the location in the pallet Vec by pallet index.
pallets_by_index: HashMap<u8, usize>,
/// Metadata of the extrinsic.
@@ -65,7 +73,7 @@ pub struct Metadata {
/// The type Id of the `DispatchError` type, which Subxt makes use of.
dispatch_error_ty: Option<u32>,
/// Details about each of the runtime API traits.
apis: OrderedMap<ArcStr, RuntimeApiMetadataInner>,
apis: OrderedMap<String, RuntimeApiMetadataInner>,
/// Allows users to add custom types to the metadata. A map that associates a string key to a `CustomValueMetadata`.
custom: CustomMetadataInner,
}
@@ -75,7 +83,7 @@ pub struct Metadata {
impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
type TypeId = u32;
fn get_call_info(
fn extrinsic_call_info(
&self,
pallet_index: u8,
call_index: u8,
@@ -108,7 +116,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
})
}
fn get_signature_info(
fn extrinsic_signature_info(
&self,
) -> Result<ExtrinsicSignatureInfo<Self::TypeId>, ExtrinsicInfoError<'_>> {
Ok(ExtrinsicSignatureInfo {
@@ -117,7 +125,7 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
})
}
fn get_extension_info(
fn extrinsic_extension_info(
&self,
extension_version: Option<u8>,
) -> Result<ExtrinsicExtensionInfo<'_, Self::TypeId>, ExtrinsicInfoError<'_>> {
@@ -142,8 +150,203 @@ impl frame_decode::extrinsics::ExtrinsicTypeInfo for Metadata {
Ok(ExtrinsicExtensionInfo { extension_ids })
}
}
impl frame_decode::storage::StorageTypeInfo for Metadata {
type TypeId = u32;
fn storage_info(
&self,
pallet_name: &str,
storage_entry: &str,
) -> Result<StorageInfo<'_, Self::TypeId>, StorageInfoError<'_>> {
let pallet =
self.pallet_by_name(pallet_name)
.ok_or_else(|| StorageInfoError::PalletNotFound {
pallet_name: pallet_name.to_string(),
})?;
let entry = pallet
.storage()
.and_then(|storage| storage.entry_by_name(storage_entry))
.ok_or_else(|| StorageInfoError::StorageNotFound {
name: storage_entry.to_string(),
pallet_name: Cow::Borrowed(pallet.name()),
})?;
let info = StorageInfo {
keys: Cow::Borrowed(&*entry.info.keys),
value_id: entry.info.value_id,
default_value: entry
.info
.default_value
.as_ref()
.map(|def| Cow::Borrowed(&**def)),
};
Ok(info)
}
fn storage_entries(&self) -> impl Iterator<Item = StorageEntry<'_>> {
self.pallets().flat_map(|pallet| {
let pallet_name = pallet.name();
pallet.storage().into_iter().flat_map(|storage| {
storage.entries().iter().map(|entry| StorageEntry {
pallet_name: Cow::Borrowed(pallet_name),
storage_entry: Cow::Borrowed(entry.name()),
})
})
})
}
}
impl frame_decode::runtime_apis::RuntimeApiTypeInfo for Metadata {
type TypeId = u32;
fn runtime_api_info(
&self,
trait_name: &str,
method_name: &str,
) -> Result<RuntimeApiInfo<'_, Self::TypeId>, RuntimeApiInfoError<'_>> {
let api_trait =
self.apis
.get_by_key(trait_name)
.ok_or_else(|| RuntimeApiInfoError::TraitNotFound {
trait_name: trait_name.to_string(),
})?;
let api_method = api_trait.methods.get_by_key(method_name).ok_or_else(|| {
RuntimeApiInfoError::MethodNotFound {
trait_name: Cow::Borrowed(&api_trait.name),
method_name: method_name.to_string(),
}
})?;
let info = RuntimeApiInfo {
inputs: Cow::Borrowed(&api_method.info.inputs),
output_id: api_method.info.output_id,
};
Ok(info)
}
fn runtime_apis(&self) -> impl Iterator<Item = RuntimeApi<'_>> {
self.runtime_api_traits().flat_map(|api_trait| {
let trait_name = api_trait.name();
api_trait.methods().map(|method| RuntimeApi {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method.name()),
})
})
}
}
impl frame_decode::view_functions::ViewFunctionTypeInfo for Metadata {
type TypeId = u32;
fn view_function_info(
&self,
pallet_name: &str,
function_name: &str,
) -> Result<ViewFunctionInfo<'_, Self::TypeId>, ViewFunctionInfoError<'_>> {
let pallet = self.pallet_by_name(pallet_name).ok_or_else(|| {
ViewFunctionInfoError::PalletNotFound {
pallet_name: pallet_name.to_string(),
}
})?;
let function = pallet.view_function_by_name(function_name).ok_or_else(|| {
ViewFunctionInfoError::FunctionNotFound {
pallet_name: Cow::Borrowed(pallet.name()),
function_name: function_name.to_string(),
}
})?;
let info = ViewFunctionInfo {
inputs: Cow::Borrowed(&function.inner.info.inputs),
output_id: function.inner.info.output_id,
query_id: *function.query_id(),
};
Ok(info)
}
fn view_functions(&self) -> impl Iterator<Item = ViewFunction<'_>> {
self.pallets().flat_map(|pallet| {
let pallet_name = pallet.name();
pallet.view_functions().map(|function| ViewFunction {
pallet_name: Cow::Borrowed(pallet_name),
function_name: Cow::Borrowed(function.name()),
})
})
}
}
impl frame_decode::constants::ConstantTypeInfo for Metadata {
type TypeId = u32;
fn constant_info(
&self,
pallet_name: &str,
constant_name: &str,
) -> Result<ConstantInfo<'_, Self::TypeId>, ConstantInfoError<'_>> {
let pallet =
self.pallet_by_name(pallet_name)
.ok_or_else(|| ConstantInfoError::PalletNotFound {
pallet_name: pallet_name.to_string(),
})?;
let constant = pallet.constant_by_name(constant_name).ok_or_else(|| {
ConstantInfoError::ConstantNotFound {
pallet_name: Cow::Borrowed(pallet.name()),
constant_name: constant_name.to_string(),
}
})?;
let info = ConstantInfo {
bytes: &constant.value,
type_id: constant.ty,
};
Ok(info)
}
fn constants(&self) -> impl Iterator<Item = Constant<'_>> {
self.pallets().flat_map(|pallet| {
let pallet_name = pallet.name();
pallet.constants().map(|constant| Constant {
pallet_name: Cow::Borrowed(pallet_name),
constant_name: Cow::Borrowed(constant.name()),
})
})
}
}
impl frame_decode::custom_values::CustomValueTypeInfo for Metadata {
type TypeId = u32;
fn custom_value_info(
&self,
name: &str,
) -> Result<CustomValueInfo<'_, Self::TypeId>, CustomValueInfoError> {
let custom_value = self
.custom()
.get(name)
.ok_or_else(|| CustomValueInfoError {
not_found: name.to_string(),
})?;
let info = CustomValueInfo {
bytes: custom_value.data,
type_id: custom_value.type_id,
};
Ok(info)
}
fn custom_values(&self) -> impl Iterator<Item = CustomValue<'_>> {
self.custom.map.keys().map(|name| CustomValue {
name: Cow::Borrowed(name),
})
}
}
impl Metadata {
/// This is essentiall an alias for `<Metadata as codec::Decode>::decode(&mut bytes)`
pub fn decode_from(mut bytes: &[u8]) -> Result<Self, codec::Error> {
<Self as codec::Decode>::decode(&mut bytes)
}
/// Access the underlying type registry.
pub fn types(&self) -> &PortableRegistry {
&self.types
@@ -217,20 +420,6 @@ impl Metadata {
})
}
/// Access a view function given its query ID, if any.
pub fn view_function_by_query_id(
&'_ self,
query_id: &[u8; 32],
) -> Option<ViewFunctionMetadata<'_>> {
// Dev note: currently, we only have pallet view functions, and here
// we just do a naive thing of iterating over the pallets to find the one
// we're looking for. Eventually we should construct a separate map of view
// functions for easy querying here.
self.pallets()
.flat_map(|p| p.view_functions())
.find(|vf| vf.query_id() == query_id)
}
/// Returns custom user defined types
pub fn custom(&self) -> CustomMetadata<'_> {
CustomMetadata {
@@ -418,7 +607,7 @@ impl<'a> PalletMetadata<'a> {
#[derive(Debug, Clone)]
struct PalletMetadataInner {
/// Pallet name.
name: ArcStr,
name: String,
/// Pallet index.
index: u8,
/// Pallet storage metadata.
@@ -436,9 +625,9 @@ struct PalletMetadataInner {
/// Error variants by name/u8.
error_variant_index: VariantIndex,
/// Map from constant name to constant details.
constants: OrderedMap<ArcStr, ConstantMetadata>,
constants: OrderedMap<String, ConstantMetadata>,
/// Details about each of the pallet view functions.
view_functions: OrderedMap<ArcStr, ViewFunctionMetadataInner>,
view_functions: OrderedMap<String, ViewFunctionMetadataInner>,
/// Mapping from associated type to type ID describing its shape.
associated_types: BTreeMap<String, u32>,
/// Pallet documentation.
@@ -451,7 +640,7 @@ pub struct StorageMetadata {
/// The common prefix used by all storage entries.
prefix: String,
/// Map from storage entry name to details.
entries: OrderedMap<ArcStr, StorageEntryMetadata>,
entries: OrderedMap<String, StorageEntryMetadata>,
}
impl StorageMetadata {
@@ -475,13 +664,9 @@ impl StorageMetadata {
#[derive(Debug, Clone)]
pub struct StorageEntryMetadata {
/// Variable name of the storage entry.
name: ArcStr,
/// An `Option` modifier of that storage entry.
modifier: StorageEntryModifier,
/// Type of the value stored in the entry.
entry_type: StorageEntryType,
/// Default value (SCALE encoded).
default: Vec<u8>,
name: String,
/// Information about the storage entry.
info: StorageInfo<'static, u32>,
/// Storage entry documentation.
docs: Vec<String>,
}
@@ -491,17 +676,18 @@ impl StorageEntryMetadata {
pub fn name(&self) -> &str {
&self.name
}
/// Is the entry value optional or does it have a default value.
pub fn modifier(&self) -> StorageEntryModifier {
self.modifier
/// Keys in this storage entry.
pub fn keys(&self) -> impl ExactSizeIterator<Item = &StorageKeyInfo<u32>> {
let keys = &*self.info.keys;
keys.iter()
}
/// Type of the storage entry.
pub fn entry_type(&self) -> &StorageEntryType {
&self.entry_type
/// Value type for this storage entry.
pub fn value_ty(&self) -> u32 {
self.info.value_id
}
/// The SCALE encoded default value for this entry.
pub fn default_bytes(&self) -> &[u8] {
&self.default
/// The default value, if one exists, for this entry.
pub fn default_value(&self) -> Option<&[u8]> {
self.info.default_value.as_deref()
}
/// Storage entry documentation.
pub fn docs(&self) -> &[String] {
@@ -509,101 +695,11 @@ impl StorageEntryMetadata {
}
}
/// The type of a storage entry.
#[derive(Debug, Clone)]
pub enum StorageEntryType {
/// Plain storage entry (just the value).
Plain(u32),
/// A storage map.
Map {
/// One or more hashers, should be one hasher per key element.
hashers: Vec<StorageHasher>,
/// The type of the key, can be a tuple with elements for each of the hashers.
key_ty: u32,
/// The type of the value.
value_ty: u32,
},
}
impl StorageEntryType {
/// The type of the value.
pub fn value_ty(&self) -> u32 {
match self {
StorageEntryType::Map { value_ty, .. } | StorageEntryType::Plain(value_ty) => *value_ty,
}
}
/// The type of the key, can be a tuple with elements for each of the hashers. None for a Plain storage entry.
pub fn key_ty(&self) -> Option<u32> {
match self {
StorageEntryType::Map { key_ty, .. } => Some(*key_ty),
StorageEntryType::Plain(_) => None,
}
}
}
/// Hasher used by storage maps.
#[derive(Debug, Clone, Copy)]
pub enum StorageHasher {
/// 128-bit Blake2 hash.
Blake2_128,
/// 256-bit Blake2 hash.
Blake2_256,
/// Multiple 128-bit Blake2 hashes concatenated.
Blake2_128Concat,
/// 128-bit XX hash.
Twox128,
/// 256-bit XX hash.
Twox256,
/// Multiple 64-bit XX hashes concatenated.
Twox64Concat,
/// Identity hashing (no hashing).
Identity,
}
impl StorageHasher {
/// The hash produced by a [`StorageHasher`] can have these two components, in order:
///
/// 1. A fixed size hash. (not present for [`StorageHasher::Identity`]).
/// 2. The SCALE encoded key that was used as an input to the hasher (only present for
/// [`StorageHasher::Twox64Concat`], [`StorageHasher::Blake2_128Concat`] or [`StorageHasher::Identity`]).
///
/// This function returns the number of bytes used to represent the first of these.
pub fn len_excluding_key(&self) -> usize {
match self {
StorageHasher::Blake2_128Concat => 16,
StorageHasher::Twox64Concat => 8,
StorageHasher::Blake2_128 => 16,
StorageHasher::Blake2_256 => 32,
StorageHasher::Twox128 => 16,
StorageHasher::Twox256 => 32,
StorageHasher::Identity => 0,
}
}
/// Returns true if the key used to produce the hash is appended to the hash itself.
pub fn ends_with_key(&self) -> bool {
matches!(
self,
StorageHasher::Blake2_128Concat | StorageHasher::Twox64Concat | StorageHasher::Identity
)
}
}
/// Is the storage entry optional, or does it have a default value.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum StorageEntryModifier {
/// The storage entry returns an `Option<T>`, with `None` if the key is not present.
Optional,
/// The storage entry returns `T::Default` if the key is not present.
Default,
}
/// Metadata for a single constant.
#[derive(Debug, Clone)]
pub struct ConstantMetadata {
/// Name of the pallet constant.
name: ArcStr,
name: String,
/// Type of the pallet constant.
ty: u32,
/// Value stored in the constant (SCALE encoded).
@@ -816,9 +912,9 @@ impl<'a> RuntimeApiMetadata<'a> {
#[derive(Debug, Clone)]
struct RuntimeApiMetadataInner {
/// Trait name.
name: ArcStr,
name: String,
/// Trait methods.
methods: OrderedMap<ArcStr, RuntimeApiMethodMetadataInner>,
methods: OrderedMap<String, RuntimeApiMethodMetadataInner>,
/// Trait documentation.
docs: Vec<String>,
}
@@ -841,12 +937,15 @@ impl<'a> RuntimeApiMethodMetadata<'a> {
&self.inner.docs
}
/// Method inputs.
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> + use<'a> {
self.inner.inputs.iter()
pub fn inputs(
&self,
) -> impl ExactSizeIterator<Item = &'a RuntimeApiInput<'static, u32>> + use<'a> {
let inputs = &*self.inner.info.inputs;
inputs.iter()
}
/// Method return type.
pub fn output_ty(&self) -> u32 {
self.inner.output_ty
self.inner.info.output_id
}
/// Return a hash for the method.
pub fn hash(&self) -> [u8; HASH_LEN] {
@@ -857,11 +956,9 @@ impl<'a> RuntimeApiMethodMetadata<'a> {
#[derive(Debug, Clone)]
struct RuntimeApiMethodMetadataInner {
/// Method name.
name: ArcStr,
/// Method parameters.
inputs: Vec<MethodParamMetadata>,
/// Method output type.
output_ty: u32,
name: String,
/// Info.
info: RuntimeApiInfo<'static, u32>,
/// Method documentation.
docs: Vec<String>,
}
@@ -882,19 +979,22 @@ impl<'a> ViewFunctionMetadata<'a> {
/// Query ID. This is used to query the function. Roughly, it is constructed by doing
/// `twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")` .
pub fn query_id(&self) -> &'a [u8; 32] {
&self.inner.query_id
&self.inner.info.query_id
}
/// Method documentation.
pub fn docs(&self) -> &'a [String] {
&self.inner.docs
}
/// Method inputs.
pub fn inputs(&self) -> impl ExactSizeIterator<Item = &'a MethodParamMetadata> + use<'a> {
self.inner.inputs.iter()
pub fn inputs(
&self,
) -> impl ExactSizeIterator<Item = &'a ViewFunctionInput<'static, u32>> + use<'a> {
let inputs = &*self.inner.info.inputs;
inputs.iter()
}
/// Method return type.
pub fn output_ty(&self) -> u32 {
self.inner.output_ty
self.inner.info.output_id
}
/// Return a hash for the method. The query ID of a view function validates it to some
/// degree, but only takes type _names_ into account. This hash takes into account the
@@ -907,13 +1007,9 @@ impl<'a> ViewFunctionMetadata<'a> {
#[derive(Debug, Clone)]
struct ViewFunctionMetadataInner {
/// View function name.
name: ArcStr,
/// View function query ID.
query_id: [u8; 32],
/// Input types.
inputs: Vec<MethodParamMetadata>,
/// Output type.
output_ty: u32,
name: String,
/// Info.
info: ViewFunctionInfo<'static, u32>,
/// Documentation.
docs: Vec<String>,
}
+13 -24
View File
@@ -6,8 +6,7 @@
use crate::{
CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, Metadata, PalletMetadata,
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, StorageEntryType,
ViewFunctionMetadata,
RuntimeApiMetadata, RuntimeApiMethodMetadata, StorageEntryMetadata, ViewFunctionMetadata,
};
use alloc::vec::Vec;
use hashbrown::HashMap;
@@ -290,29 +289,19 @@ fn get_extrinsic_hash(registry: &PortableRegistry, extrinsic: &ExtrinsicMetadata
fn get_storage_entry_hash(registry: &PortableRegistry, entry: &StorageEntryMetadata) -> Hash {
let mut bytes = concat_and_hash3(
&hash(entry.name.as_bytes()),
// Cloning 'entry.modifier' should essentially be a copy.
&[entry.modifier as u8; HASH_LEN],
&hash(&entry.default),
&get_type_hash(registry, entry.info.value_id),
&hash(entry.info.default_value.as_deref().unwrap_or_default()),
);
match &entry.entry_type {
StorageEntryType::Plain(ty) => concat_and_hash2(&bytes, &get_type_hash(registry, *ty)),
StorageEntryType::Map {
hashers,
key_ty,
value_ty,
} => {
for hasher in hashers {
// Cloning the hasher should essentially be a copy.
bytes = concat_and_hash2(&bytes, &[*hasher as u8; HASH_LEN]);
}
concat_and_hash3(
&bytes,
&get_type_hash(registry, *key_ty),
&get_type_hash(registry, *value_ty),
)
}
for key in &*entry.info.keys {
bytes = concat_and_hash3(
&bytes,
&[key.hasher as u8; HASH_LEN],
&get_type_hash(registry, key.key_id),
)
}
bytes
}
fn get_custom_metadata_hash(custom_metadata: &CustomMetadata) -> Hash {
@@ -382,7 +371,7 @@ pub fn get_runtime_api_hash(runtime_api: &RuntimeApiMethodMetadata) -> Hash {
bytes = concat_and_hash3(
&bytes,
&hash(input.name.as_bytes()),
&get_type_hash(registry, input.ty),
&get_type_hash(registry, input.id),
);
}
@@ -419,7 +408,7 @@ pub fn get_view_function_hash(view_function: &ViewFunctionMetadata) -> Hash {
bytes = concat_and_hash3(
&bytes,
&hash(input.name.as_bytes()),
&get_type_hash(registry, input.ty),
&get_type_hash(registry, input.id),
);
}
+3 -3
View File
@@ -21,10 +21,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
continue; // we do not look at inherents in this example
};
let meta = ext.extrinsic_metadata()?;
let fields = ext.field_values()?;
// Decode the fields into our dynamic Value type to display:
let fields = ext.decode_as_fields::<scale_value::Value>()?;
println!(" {}/{}", meta.pallet.name(), meta.variant.name);
println!(" {}/{}", ext.pallet_name(), ext.call_name());
println!(" Transaction Extensions:");
for signed_ext in transaction_extensions.iter() {
// We only want to take a look at these 3 signed extensions, because the others all just have unit fields.
+1 -1
View File
@@ -42,7 +42,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let evt = evt?;
let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.field_values()?;
let event_values = evt.decode_as_fields::<scale_value::Value>()?;
println!(" {pallet_name}_{event_name}");
println!(" {event_values}");
+13 -5
View File
@@ -1,4 +1,5 @@
#![allow(missing_docs)]
use subxt::dynamic::Value;
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
@@ -6,13 +7,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// A dynamic query to obtain some constant:
let constant_query = subxt::dynamic::constant("System", "BlockLength");
// We can query a constant by providing a tuple of the pallet and constant name. The return type
// will be `Value` if we pass this query:
let constant_query = ("System", "BlockLength");
let _value = api.constants().at(&constant_query)?;
// Obtain the value:
// Or we can use the library function to query a constant, which allows us to pass a generic type
// that Subxt will attempt to decode the constant into:
let constant_query = subxt::dynamic::constant::<Value>("System", "BlockLength");
let value = api.constants().at(&constant_query)?;
println!("Constant bytes: {:?}", value.encoded());
println!("Constant value: {}", value.to_value()?);
// Or we can obtain the bytes for the constant, using either form of query.
let bytes = api.constants().bytes_at(&constant_query)?;
println!("Constant bytes: {:?}", bytes);
println!("Constant value: {}", value);
Ok(())
}
+4
View File
@@ -15,6 +15,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Obtain the value:
let value = api.constants().at(&constant_query)?;
// Or obtain the bytes:
let bytes = api.constants().bytes_at(&constant_query)?;
println!("Encoded block length: {bytes:?}");
println!("Block length: {value:?}");
Ok(())
}
+1 -1
View File
@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pallet = event.pallet_name();
let variant = event.variant_name();
let field_values = event.field_values()?;
let field_values = event.decode_as_fields::<scale_value::Value>()?;
println!("{pallet}::{variant}: {field_values}");
}
+10 -10
View File
@@ -1,5 +1,5 @@
#![allow(missing_docs)]
use subxt::dynamic::Value;
use subxt::utils::AccountId32;
use subxt::{OnlineClient, config::PolkadotConfig};
use subxt_signer::sr25519::dev;
@@ -8,14 +8,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a dynamically runtime API payload that calls the
// `AccountNonceApi_account_nonce` function.
let account = dev::alice().public_key();
let runtime_api_call = subxt::dynamic::runtime_api_call(
"AccountNonceApi",
"account_nonce",
vec![Value::from_bytes(account)],
);
// Create a "dynamic" runtime API payload that calls the
// `AccountNonceApi_account_nonce` function. We could use the
// `scale_value::Value` type as output, and a vec of those as inputs,
// but since we know the input + return types we can pass them directly.
// There is one input argument, so the inputs are a tuple of one element.
let account: AccountId32 = dev::alice().public_key().into();
let runtime_api_call =
subxt::dynamic::runtime_api_call::<_, u64>("AccountNonceApi", "account_nonce", (account,));
// Submit the call to get back a result.
let nonce = api
@@ -25,6 +25,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.call(runtime_api_call)
.await?;
println!("Account nonce: {:#?}", nonce.to_value());
println!("Account nonce: {:#?}", nonce);
Ok(())
}
+13 -12
View File
@@ -10,22 +10,23 @@ pub mod polkadot {}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
let account = dev::alice().public_key().into();
// Build a storage query to access account information.
let account = dev::alice().public_key().into();
let storage_query = polkadot::storage().system().account(account);
let storage_query = polkadot::storage().system().account();
// Use that query to `fetch` a result. This returns an `Option<_>`, which will be
// `None` if no value exists at the given address. You can also use `fetch_default`
// where applicable, which will return the default value if none exists.
let result = api
.storage()
.at_latest()
// Use that query to access a storage entry, fetch a result and decode the value.
// The static address knows that fetching requires a tuple of one value, an
// AccountId32.
let client_at = api.storage().at_latest().await?;
let account_info = client_at
.entry(storage_query)?
.fetch((account,))
.await?
.fetch(&storage_query)
.await?;
.decode()?;
let v = result.unwrap().data.free;
println!("Alice: {v}");
// The static address that we got from the subxt macro knows the expected input
// and return types, so it is decoded into a static type for us.
println!("Alice: {account_info:?}");
Ok(())
}
+18 -12
View File
@@ -1,5 +1,6 @@
#![allow(missing_docs)]
use subxt::dynamic::{At, Value};
use subxt::utils::AccountId32;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
@@ -9,20 +10,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a dynamic storage query to access account information.
let account = dev::alice().public_key();
let storage_query =
subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
// here, we assume that there is one value to provide at this entry
// to access a value; an AccountId32. In this example we don't know the
// return type and so we set it to `Value`, which anything can decode into.
let account: AccountId32 = dev::alice().public_key().into();
let storage_query = subxt::dynamic::storage::<(AccountId32,), Value>("System", "Account");
// Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result
// type will be either, and so we get a type back that can be decoded into a dynamic Value type.
let result = api
.storage()
.at_latest()
// Use that query to access a storage entry, fetch a result and decode the value.
let client_at = api.storage().at_latest().await?;
let account_info = client_at
.entry(storage_query)?
.fetch((account,))
.await?
.fetch(&storage_query)
.await?;
let value = result.unwrap().to_value()?;
.decode()?;
println!("Alice has free balance: {:?}", value.at("data").at("free"));
// With out `Value` type we can dig in to find what we want using the `At`
// trait and `.at()` method that this provides on the Value.
println!(
"Alice has free balance: {}",
account_info.at("data").at("free").unwrap()
);
Ok(())
}
+26 -10
View File
@@ -1,7 +1,9 @@
#![allow(missing_docs)]
use subxt::ext::futures::StreamExt;
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
@@ -9,17 +11,31 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a storage query to iterate over account information.
let storage_query = polkadot::storage().system().account_iter();
// Build a storage query to access account information. Same as if we were
// fetching a single value from this entry.
let storage_query = polkadot::storage().system().account();
// Get back an iterator of results (here, we are fetching 10 items at
// a time from the node, but we always iterate over one at a time).
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
// Use that query to access a storage entry, iterate over it and decode values.
let client_at = api.storage().at_latest().await?;
while let Some(Ok(kv)) = results.next().await {
println!("Keys decoded: {:?}", kv.keys);
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
println!("Value: {:?}", kv.value);
// We provide an empty tuple when iterating. If the storage entry had been an N map with
// multiple keys, then we could provide any prefix of those keys to iterate over. This is
// statically type checked, so only a valid number/type of keys in the tuple is accepted.
let mut values = client_at.entry(storage_query)?.iter(()).await?;
while let Some(kv) = values.next().await {
let kv = kv?;
// The key decodes into the type that the static address knows about, in this case a
// tuple of one entry, because the only part of the key that we can decode is the
// AccountId32 for each user.
let (account_id32,) = kv.key()?.decode()?;
// The value decodes into a statically generated type which holds account information.
let value = kv.value().decode()?;
let value_data = value.data;
println!("{account_id32}:\n {value_data:?}");
}
Ok(())
+29 -11
View File
@@ -1,23 +1,41 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig, dynamic::Value};
use subxt::ext::futures::StreamExt;
use subxt::utils::AccountId32;
use subxt::{
OnlineClient, PolkadotConfig,
dynamic::{At, Value},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a dynamic storage query to iterate account information.
// With a dynamic query, we can just provide an empty vector as the keys to iterate over all entries.
let keys: Vec<Value> = vec![];
let storage_query = subxt::dynamic::storage("System", "Account", keys);
// Build a dynamic storage query to access account information.
// here, we assume that there is one value to provide at this entry
// to access a value; an AccountId32. In this example we don't know the
// return type and so we set it to `Value`, which anything can decode into.
let storage_query = subxt::dynamic::storage::<(AccountId32,), Value>("System", "Account");
// Use that query to return an iterator over the results.
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
// Use that query to access a storage entry, iterate over it and decode values.
let client_at = api.storage().at_latest().await?;
let mut values = client_at.entry(storage_query)?.iter(()).await?;
while let Some(Ok(kv)) = results.next().await {
println!("Keys decoded: {:?}", kv.keys);
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
println!("Value: {:?}", kv.value.to_value()?);
while let Some(kv) = values.next().await {
let kv = kv?;
// The key decodes into the first type we provided in the address. Since there's just
// one key, it is a tuple of one entry, an AccountId32. If we didn't know how many
// keys or their type, we could set the key to `Vec<Value>` instead.
let (account_id32,) = kv.key()?.decode()?;
// The value decodes into the second type we provided in the address. In this example,
// we just decode it into our `Value` type and then look at the "data" field in this
// (which implicitly assumes we get a struct shaped thing back with such a field).
let value = kv.value().decode()?;
let value_data = value.at("data").unwrap();
println!("{account_id32}:\n {value_data}");
}
Ok(())
@@ -1,77 +0,0 @@
#![allow(missing_docs)]
use polkadot::multisig::events::NewMultisig;
use polkadot::runtime_types::{
frame_system::pallet::Call, rococo_runtime::RuntimeCall, sp_weights::weight_v2::Weight,
};
use subxt::utils::AccountId32;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::{Keypair, dev};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Prepare the chain to have 3 open multisig requests (2 of them are alice + bob):
let alice_signer = dev::alice();
let bob = AccountId32(dev::bob().public_key().0);
let charlie = AccountId32(dev::charlie().public_key().0);
let new_multisig_1 = submit_remark_as_multi(&alice_signer, &bob, b"Hello", &api).await?;
let new_multisig_2 = submit_remark_as_multi(&alice_signer, &bob, b"Hi", &api).await?;
let new_multisig_3 = submit_remark_as_multi(&alice_signer, &charlie, b"Hello", &api).await?;
// Note: the NewMultisig event contains the multisig address we need to use for the storage queries:
assert_eq!(new_multisig_1.multisig, new_multisig_2.multisig);
assert_ne!(new_multisig_1.multisig, new_multisig_3.multisig);
// Build a storage query to iterate over open multisig extrinsics from
// new_multisig_1.multisig which is the AccountId of the alice + bob multisig account
let alice_bob_account_id = new_multisig_1.multisig;
let storage_query = polkadot::storage()
.multisig()
.multisigs_iter1(alice_bob_account_id);
// Get back an iterator of results.
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
while let Some(Ok(kv)) = results.next().await {
println!("Keys decoded: {:?}", kv.keys);
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
println!("Value: {:?}", kv.value);
}
Ok(())
}
async fn submit_remark_as_multi(
signer: &Keypair,
other: &AccountId32,
remark: &[u8],
api: &OnlineClient<PolkadotConfig>,
) -> Result<NewMultisig, Box<dyn std::error::Error>> {
let multisig_remark_tx = polkadot::tx().multisig().as_multi(
2,
vec![other.clone()],
None,
RuntimeCall::System(Call::remark {
remark: remark.to_vec(),
}),
Weight {
ref_time: 0,
proof_size: 0,
},
);
let events = api
.tx()
.sign_and_submit_then_watch_default(&multisig_remark_tx, signer)
.await?
.wait_for_finalized_success()
.await?;
let new_multisig = events
.find_first::<polkadot::multisig::events::NewMultisig>()?
.expect("should contain event");
Ok(new_multisig)
}
+11 -7
View File
@@ -3,7 +3,7 @@
// see LICENSE for license details.
use crate::config::{Config, HashFor};
use crate::error::Error;
use crate::error::BackendError;
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
use std::future::Future;
use std::pin::Pin;
@@ -34,12 +34,16 @@ pub type FollowEventStreamGetter<Hash> = Box<dyn FnMut() -> FollowEventStreamFut
/// The future which will return a stream of follow events and the subscription ID for it.
pub type FollowEventStreamFut<Hash> = Pin<
Box<dyn Future<Output = Result<(FollowEventStream<Hash>, String), Error>> + Send + 'static>,
Box<
dyn Future<Output = Result<(FollowEventStream<Hash>, String), BackendError>>
+ Send
+ 'static,
>,
>;
/// The stream of follow events.
pub type FollowEventStream<Hash> =
Pin<Box<dyn Stream<Item = Result<FollowEvent<Hash>, Error>> + Send + 'static>>;
Pin<Box<dyn Stream<Item = Result<FollowEvent<Hash>, BackendError>> + Send + 'static>>;
/// Either a ready message with the current subscription ID, or
/// an event from the stream itself.
@@ -108,7 +112,7 @@ impl<Hash> FollowStream<Hash> {
let stream = methods.chainhead_v1_follow(true).await?;
// Extract the subscription ID:
let Some(sub_id) = stream.subscription_id().map(ToOwned::to_owned) else {
return Err(Error::Other(
return Err(BackendError::Other(
"Subscription ID expected for chainHead_follow response, but not given"
.to_owned(),
));
@@ -128,7 +132,7 @@ impl<Hash> FollowStream<Hash> {
impl<Hash> std::marker::Unpin for FollowStream<Hash> {}
impl<Hash> Stream for FollowStream<Hash> {
type Item = Result<FollowStreamMsg<Hash>, Error>;
type Item = Result<FollowStreamMsg<Hash>, BackendError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
@@ -228,7 +232,7 @@ pub(super) mod test_utils {
where
Hash: Send + 'static,
F: Fn() -> I + Send + 'static,
I: IntoIterator<Item = Result<FollowEvent<Hash>, Error>>,
I: IntoIterator<Item = Result<FollowEvent<Hash>, BackendError>>,
{
let start_idx = Arc::new(AtomicUsize::new(0));
@@ -307,7 +311,7 @@ pub mod test {
Ok(FollowEvent::Stop),
Ok(ev_new_block(1, 2)),
// Nothing should be emitted after an error:
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
Ok(ev_new_block(2, 3)),
]
});
@@ -4,7 +4,7 @@
use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin};
use crate::config::Hash;
use crate::error::{Error, RpcError};
use crate::error::{BackendError, RpcError};
use futures::stream::{Stream, StreamExt};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ops::DerefMut;
@@ -42,7 +42,7 @@ impl<H: Hash> FollowStreamDriver<H> {
}
impl<H: Hash> Stream for FollowStreamDriver<H> {
type Item = Result<(), Error>;
type Item = Result<(), BackendError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.inner.poll_next_unpin(cx) {
@@ -421,7 +421,7 @@ where
H: Hash,
F: Fn(FollowEvent<BlockRef<H>>) -> Vec<BlockRef<H>>,
{
type Item = Result<(String, Vec<BlockRef<H>>), Error>;
type Item = Result<(String, Vec<BlockRef<H>>), BackendError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.is_done {
@@ -500,7 +500,7 @@ mod test_utils {
where
H: Hash + 'static,
F: Fn() -> I + Send + 'static,
I: IntoIterator<Item = Result<FollowEvent<H>, Error>>,
I: IntoIterator<Item = Result<FollowEvent<H>, BackendError>>,
{
let (stream, _) = test_unpin_stream_getter(events, max_life);
FollowStreamDriver::new(stream)
@@ -537,7 +537,7 @@ mod test {
Ok(ev_new_block(0, 1)),
Ok(ev_best_block(1)),
Ok(ev_finalized([1], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -580,7 +580,7 @@ mod test {
Ok(ev_finalized([1], [])),
Ok(ev_new_block(1, 2)),
Ok(ev_new_block(2, 3)),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -630,7 +630,7 @@ mod test {
Ok(ev_new_block(1, 2)),
Ok(ev_new_block(2, 3)),
Ok(ev_finalized([1], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -668,7 +668,7 @@ mod test {
Ok(FollowEvent::Stop),
Ok(ev_initialized(1)),
Ok(ev_finalized([2], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -714,7 +714,7 @@ mod test {
// Emulate that we missed some blocks.
Ok(ev_initialized(13)),
Ok(ev_finalized([14], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -742,7 +742,7 @@ mod test {
)
);
assert!(
matches!(&evs[1], Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(e)))) if e.contains("Missed at least one block when the connection was lost"))
matches!(&evs[1], Err(BackendError::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(e)))) if e.contains("Missed at least one block when the connection was lost"))
);
assert_eq!(
evs[2].as_ref().unwrap(),
@@ -5,7 +5,7 @@
use super::ChainHeadRpcMethods;
use super::follow_stream::FollowStream;
use crate::config::{Config, Hash, HashFor};
use crate::error::Error;
use crate::error::BackendError;
use futures::stream::{FuturesUnordered, Stream, StreamExt};
use subxt_rpcs::methods::chain_head::{
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock,
@@ -71,7 +71,7 @@ pub type UnpinFut = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
impl<H: Hash> std::marker::Unpin for FollowStreamUnpin<H> {}
impl<H: Hash> Stream for FollowStreamUnpin<H> {
type Item = Result<FollowStreamMsg<BlockRef<H>>, Error>;
type Item = Result<FollowStreamMsg<BlockRef<H>>, BackendError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut();
@@ -482,7 +482,7 @@ pub(super) mod test_utils {
where
H: Hash + 'static,
F: Fn() -> I + Send + 'static,
I: IntoIterator<Item = Result<FollowEvent<H>, Error>>,
I: IntoIterator<Item = Result<FollowEvent<H>, BackendError>>,
{
// Unpin requests will come here so that we can look out for them.
let (unpin_tx, unpin_rx) = std::sync::mpsc::channel();
@@ -567,7 +567,7 @@ mod test {
Ok(ev_new_block(0, 1)),
Ok(ev_new_block(1, 2)),
Ok(ev_new_block(2, 3)),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -593,7 +593,7 @@ mod test {
[
Ok(ev_initialized(0)),
Ok(ev_finalized([1], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
3,
@@ -624,7 +624,7 @@ mod test {
Ok(ev_finalized([3], [])),
Ok(ev_finalized([4], [])),
Ok(ev_finalized([5], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
3,
@@ -663,7 +663,7 @@ mod test {
Ok(ev_new_block(1, 2)),
Ok(ev_finalized([1], [])),
Ok(ev_finalized([2], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -711,7 +711,7 @@ mod test {
Ok(ev_finalized([1], [])),
Ok(ev_finalized([2], [3])),
Ok(ev_finalized([4], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
@@ -771,7 +771,7 @@ mod test {
Ok(ev_best_block(1)),
Ok(ev_finalized([1], [])),
Ok(ev_finalized([2], [])),
Err(Error::Other("ended".to_owned())),
Err(BackendError::Other("ended".to_owned())),
]
},
10,
+24 -22
View File
@@ -22,7 +22,7 @@ use crate::backend::{
TransactionStatus, utils::retry,
};
use crate::config::{Config, Hash, HashFor};
use crate::error::{Error, RpcError};
use crate::error::{BackendError, RpcError};
use async_trait::async_trait;
use follow_stream_driver::{FollowStreamDriver, FollowStreamDriverHandle};
use futures::future::Either;
@@ -229,7 +229,7 @@ impl<T: Config> ChainHeadBackend<T> {
async fn stream_headers<F>(
&self,
f: F,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>
where
F: Fn(
FollowEvent<follow_stream_unpin::BlockRef<HashFor<T>>>,
@@ -290,7 +290,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
&self,
keys: Vec<Vec<u8>>,
at: HashFor<T>,
) -> Result<StreamOfResults<StorageResponse>, Error> {
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
retry(|| async {
let queries = keys.iter().map(|key| StorageQuery {
key: &**key,
@@ -325,7 +325,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<StreamOfResults<Vec<u8>>, Error> {
) -> Result<StreamOfResults<Vec<u8>>, BackendError> {
retry(|| async {
// Ask for hashes, and then just ignore them and return the keys that come back.
let query = StorageQuery {
@@ -351,7 +351,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<StreamOfResults<StorageResponse>, Error> {
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
retry(|| async {
let query = StorageQuery {
key: &*key,
@@ -386,7 +386,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
.await
}
async fn genesis_hash(&self) -> Result<HashFor<T>, Error> {
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError> {
retry(|| async {
let genesis_hash = self.methods.chainspec_v1_genesis_hash().await?;
Ok(genesis_hash)
@@ -394,7 +394,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
.await
}
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error> {
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError> {
retry(|| async {
let sub_id = get_subscription_id(&self.follow_handle).await?;
let header = self.methods.chainhead_v1_header(&sub_id, at).await?;
@@ -403,7 +403,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
.await
}
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error> {
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
retry(|| async {
let sub_id = get_subscription_id(&self.follow_handle).await?;
@@ -432,7 +432,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
.await
}
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error> {
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError> {
let next_ref: Option<BlockRef<HashFor<T>>> = self
.follow_handle
.subscribe()
@@ -452,17 +452,19 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
next_ref.ok_or_else(|| RpcError::SubscriptionDropped.into())
}
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError> {
// Just start a stream of version infos, and return the first value we get from it.
let runtime_version = self.stream_runtime_version().await?.next().await;
match runtime_version {
None => Err(Error::Rpc(RpcError::SubscriptionDropped)),
None => Err(BackendError::Rpc(RpcError::SubscriptionDropped)),
Some(Err(e)) => Err(e),
Some(Ok(version)) => Ok(version),
}
}
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
async fn stream_runtime_version(
&self,
) -> Result<StreamOfResults<RuntimeVersion>, BackendError> {
// Keep track of runtime details announced in new blocks, and then when blocks
// are finalized, find the latest of these that has runtime details, and clear the rest.
let mut runtimes = HashMap::new();
@@ -526,7 +528,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
let runtime_details = match runtime_event {
RuntimeEvent::Invalid(err) => {
return std::future::ready(Some(Err(Error::Other(err.error))))
return std::future::ready(Some(Err(BackendError::Other(format!("Invalid runtime error using chainHead RPCs: {}", err.error)))))
}
RuntimeEvent::Valid(ev) => ev,
};
@@ -544,7 +546,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
async fn stream_all_block_headers(
&self,
_hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
// TODO: https://github.com/paritytech/subxt/issues/1568
//
// It's possible that blocks may be silently missed if
@@ -562,7 +564,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
async fn stream_best_block_headers(
&self,
_hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
// TODO: https://github.com/paritytech/subxt/issues/1568
//
// It's possible that blocks may be silently missed if
@@ -578,7 +580,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
async fn stream_finalized_block_headers(
&self,
_hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
self.stream_headers(|ev| match ev {
FollowEvent::Initialized(init) => init.finalized_block_hashes,
FollowEvent::Finalized(ev) => ev.finalized_block_hashes,
@@ -590,12 +592,12 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
async fn submit_transaction(
&self,
extrinsic: &[u8],
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
// Submit a transaction. This makes no attempt to sync with follow events,
async fn submit_transaction_ignoring_follow_events<T: Config>(
extrinsic: &[u8],
methods: &ChainHeadRpcMethods<T>,
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
let tx_progress = methods
.transactionwatch_v1_submit_and_watch(extrinsic)
.await?
@@ -637,7 +639,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
transaction_timeout_secs: u64,
methods: &ChainHeadRpcMethods<T>,
follow_handle: &FollowStreamDriverHandle<HashFor<T>>,
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
// We care about new and finalized block hashes.
enum SeenBlockMarker {
New,
@@ -664,7 +666,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
let start_instant = web_time::Instant::now();
// A quick helper to return a generic error.
let err_other = |s: &str| Some(Err(Error::Other(s.into())));
let err_other = |s: &str| Some(Err(BackendError::Other(s.into())));
// Now we can attempt to associate tx events with pinned blocks.
let tx_stream = futures::stream::poll_fn(move |cx| {
@@ -828,7 +830,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
method: &str,
call_parameters: Option<&[u8]>,
at: HashFor<T>,
) -> Result<Vec<u8>, Error> {
) -> Result<Vec<u8>, BackendError> {
retry(|| async {
let sub_id = get_subscription_id(&self.follow_handle).await?;
@@ -867,7 +869,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for ChainHeadBackend<T> {
/// A helper to obtain a subscription ID.
async fn get_subscription_id<H: Hash>(
follow_handle: &FollowStreamDriverHandle<H>,
) -> Result<String, Error> {
) -> Result<String, BackendError> {
let Some(sub_id) = follow_handle.subscribe().subscription_id().await else {
return Err(RpcError::SubscriptionDropped.into());
};
@@ -5,7 +5,7 @@
use super::follow_stream_driver::FollowStreamDriverHandle;
use super::follow_stream_unpin::BlockRef;
use crate::config::{Config, HashFor};
use crate::error::{Error, RpcError};
use crate::error::{BackendError, RpcError};
use futures::{FutureExt, Stream, StreamExt};
use std::collections::VecDeque;
use std::future::Future;
@@ -36,7 +36,7 @@ impl<T: Config> StorageItems<T> {
at: HashFor<T>,
follow_handle: &FollowStreamDriverHandle<HashFor<T>>,
methods: ChainHeadRpcMethods<T>,
) -> Result<Self, Error> {
) -> Result<Self, BackendError> {
let sub_id = super::get_subscription_id(follow_handle).await?;
// Subscribe to events and make the initial request to get an operation ID.
@@ -92,10 +92,10 @@ impl<T: Config> StorageItems<T> {
pub type FollowEventStream<Hash> =
Pin<Box<dyn Stream<Item = FollowEvent<BlockRef<Hash>>> + Send + 'static>>;
pub type ContinueFutGetter = Box<dyn Fn() -> ContinueFut + Send + 'static>;
pub type ContinueFut = Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'static>>;
pub type ContinueFut = Pin<Box<dyn Future<Output = Result<(), BackendError>> + Send + 'static>>;
impl<T: Config> Stream for StorageItems<T> {
type Item = Result<StorageResult, Error>;
type Item = Result<StorageResult, BackendError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
@@ -157,7 +157,7 @@ impl<T: Config> Stream for StorageItems<T> {
FollowEvent::OperationError(err) if err.operation_id == *self.operation_id => {
// Something went wrong obtaining storage items; mark as done and return the error.
self.done = true;
return Poll::Ready(Some(Err(Error::Other(err.error))));
return Poll::Ready(Some(Err(BackendError::Other(err.error))));
}
_ => {
// We don't care about this event; wait for the next.
+27 -26
View File
@@ -11,10 +11,8 @@ use crate::backend::{
Backend, BlockRef, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults,
TransactionStatus,
};
use crate::{
Error,
config::{Config, HashFor, Header},
};
use crate::config::{Config, HashFor, Header};
use crate::error::BackendError;
use async_trait::async_trait;
use futures::TryStreamExt;
use futures::{Future, FutureExt, Stream, StreamExt, future, future::Either, stream};
@@ -101,12 +99,12 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
&self,
keys: Vec<Vec<u8>>,
at: HashFor<T>,
) -> Result<StreamOfResults<StorageResponse>, Error> {
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
fn get_entry<T: Config>(
key: Vec<u8>,
at: HashFor<T>,
methods: LegacyRpcMethods<T>,
) -> impl Future<Output = Result<Option<StorageResponse>, Error>> {
) -> impl Future<Output = Result<Option<StorageResponse>, BackendError>> {
retry(move || {
let methods = methods.clone();
let key = key.clone();
@@ -138,7 +136,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<StreamOfResults<Vec<u8>>, Error> {
) -> Result<StreamOfResults<Vec<u8>>, BackendError> {
let keys = StorageFetchDescendantKeysStream {
at,
key,
@@ -169,7 +167,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<StreamOfResults<StorageResponse>, Error> {
) -> Result<StreamOfResults<StorageResponse>, BackendError> {
let keys_stream = StorageFetchDescendantKeysStream {
at,
key,
@@ -187,7 +185,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
})))
}
async fn genesis_hash(&self) -> Result<HashFor<T>, Error> {
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError> {
retry(|| async {
let hash = self.methods.genesis_hash().await?;
Ok(hash)
@@ -195,7 +193,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
.await
}
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error> {
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError> {
retry(|| async {
let header = self.methods.chain_get_header(Some(at)).await?;
Ok(header)
@@ -203,7 +201,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
.await
}
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error> {
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError> {
retry(|| async {
let Some(details) = self.methods.chain_get_block(Some(at)).await? else {
return Ok(None);
@@ -215,7 +213,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
.await
}
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error> {
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError> {
retry(|| async {
let hash = self.methods.chain_get_finalized_head().await?;
Ok(BlockRef::from_hash(hash))
@@ -223,7 +221,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
.await
}
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError> {
retry(|| async {
let details = self.methods.state_get_runtime_version(None).await?;
Ok(RuntimeVersion {
@@ -234,7 +232,9 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
.await
}
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
async fn stream_runtime_version(
&self,
) -> Result<StreamOfResults<RuntimeVersion>, BackendError> {
let methods = self.methods.clone();
let retry_sub = retry_stream(move || {
@@ -274,7 +274,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
async fn stream_all_block_headers(
&self,
hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
let methods = self.methods.clone();
let retry_sub = retry_stream(move || {
let methods = methods.clone();
@@ -297,7 +297,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
async fn stream_best_block_headers(
&self,
hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
let methods = self.methods.clone();
let retry_sub = retry_stream(move || {
@@ -321,7 +321,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
async fn stream_finalized_block_headers(
&self,
hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
let this = self.clone();
let retry_sub = retry_stream(move || {
@@ -361,7 +361,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
async fn submit_transaction(
&self,
extrinsic: &[u8],
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error> {
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError> {
let sub = self
.methods
.author_submit_and_watch_extrinsic(extrinsic)
@@ -423,7 +423,7 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
method: &str,
call_parameters: Option<&[u8]>,
at: HashFor<T>,
) -> Result<Vec<u8>, Error> {
) -> Result<Vec<u8>, BackendError> {
retry(|| async {
let res = self
.methods
@@ -442,11 +442,11 @@ pub fn subscribe_to_block_headers_filling_in_gaps<T, S, E>(
methods: LegacyRpcMethods<T>,
sub: S,
mut last_block_num: Option<u64>,
) -> impl Stream<Item = Result<T::Header, Error>> + Send
) -> impl Stream<Item = Result<T::Header, BackendError>> + Send
where
T: Config,
S: Stream<Item = Result<T::Header, E>> + Send,
E: Into<Error> + Send + 'static,
E: Into<BackendError> + Send + 'static,
{
sub.flat_map(move |s| {
// Get the header, or return a stream containing just the error.
@@ -470,7 +470,7 @@ where
async move {
let hash = methods.chain_get_block_hash(Some(n.into())).await?;
let header = methods.chain_get_header(hash).await?;
Ok::<_, Error>(header)
Ok::<_, BackendError>(header)
}
})
.filter_map(async |h| h.transpose());
@@ -495,7 +495,8 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
// What key do we start paginating from? None = from the beginning.
pagination_start_key: Option<Vec<u8>>,
// Keys, future and cached:
keys_fut: Option<Pin<Box<dyn Future<Output = Result<Vec<Vec<u8>>, Error>> + Send + 'static>>>,
keys_fut:
Option<Pin<Box<dyn Future<Output = Result<Vec<Vec<u8>>, BackendError>> + Send + 'static>>>,
// Set to true when we're done:
done: bool,
}
@@ -503,7 +504,7 @@ pub struct StorageFetchDescendantKeysStream<T: Config> {
impl<T: Config> std::marker::Unpin for StorageFetchDescendantKeysStream<T> {}
impl<T: Config> Stream for StorageFetchDescendantKeysStream<T> {
type Item = Result<Vec<Vec<u8>>, Error>;
type Item = Result<Vec<Vec<u8>>, BackendError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut();
loop {
@@ -584,7 +585,7 @@ pub struct StorageFetchDescendantValuesStream<T: Config> {
results_fut: Option<
Pin<
Box<
dyn Future<Output = Result<Option<VecDeque<(Vec<u8>, Vec<u8>)>>, Error>>
dyn Future<Output = Result<Option<VecDeque<(Vec<u8>, Vec<u8>)>>, BackendError>>
+ Send
+ 'static,
>,
@@ -595,7 +596,7 @@ pub struct StorageFetchDescendantValuesStream<T: Config> {
}
impl<T: Config> Stream for StorageFetchDescendantValuesStream<T> {
type Item = Result<StorageResponse, Error>;
type Item = Result<StorageResponse, BackendError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.as_mut();
loop {
+39 -32
View File
@@ -11,14 +11,14 @@ pub mod legacy;
pub mod utils;
use crate::config::{Config, HashFor};
use crate::error::Error;
use crate::metadata::Metadata;
use crate::error::BackendError;
use async_trait::async_trait;
use codec::{Decode, Encode};
use futures::{Stream, StreamExt};
use std::pin::Pin;
use std::sync::Arc;
use subxt_core::client::RuntimeVersion;
use subxt_metadata::Metadata;
/// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`].
pub mod rpc {
@@ -82,66 +82,67 @@ pub trait Backend<T: Config>: sealed::Sealed + Send + Sync + 'static {
&self,
keys: Vec<Vec<u8>>,
at: HashFor<T>,
) -> Result<StreamOfResults<StorageResponse>, Error>;
) -> Result<StreamOfResults<StorageResponse>, BackendError>;
/// Fetch keys underneath the given key from storage.
async fn storage_fetch_descendant_keys(
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<StreamOfResults<Vec<u8>>, Error>;
) -> Result<StreamOfResults<Vec<u8>>, BackendError>;
/// Fetch values underneath the given key from storage.
async fn storage_fetch_descendant_values(
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<StreamOfResults<StorageResponse>, Error>;
) -> Result<StreamOfResults<StorageResponse>, BackendError>;
/// Fetch the genesis hash
async fn genesis_hash(&self) -> Result<HashFor<T>, Error>;
async fn genesis_hash(&self) -> Result<HashFor<T>, BackendError>;
/// Get a block header
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, Error>;
async fn block_header(&self, at: HashFor<T>) -> Result<Option<T::Header>, BackendError>;
/// Return the extrinsics found in the block. Each extrinsic is represented
/// by a vector of bytes which has _not_ been SCALE decoded (in other words, the
/// first bytes in the vector will decode to the compact encoded length of the extrinsic)
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, Error>;
async fn block_body(&self, at: HashFor<T>) -> Result<Option<Vec<Vec<u8>>>, BackendError>;
/// Get the most recent finalized block hash.
/// Note: needed only in blocks client for finalized block stream; can prolly be removed.
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, Error>;
async fn latest_finalized_block_ref(&self) -> Result<BlockRef<HashFor<T>>, BackendError>;
/// Get information about the current runtime.
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error>;
async fn current_runtime_version(&self) -> Result<RuntimeVersion, BackendError>;
/// A stream of all new runtime versions as they occur.
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error>;
async fn stream_runtime_version(&self)
-> Result<StreamOfResults<RuntimeVersion>, BackendError>;
/// A stream of all new block headers as they arrive.
async fn stream_all_block_headers(
&self,
hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
/// A stream of best block headers.
async fn stream_best_block_headers(
&self,
hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
/// A stream of finalized block headers.
async fn stream_finalized_block_headers(
&self,
hasher: T::Hasher,
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error>;
) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError>;
/// Submit a transaction. This will return a stream of events about it.
async fn submit_transaction(
&self,
bytes: &[u8],
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, Error>;
) -> Result<StreamOfResults<TransactionStatus<HashFor<T>>>, BackendError>;
/// Make a call to some runtime API.
async fn call(
@@ -149,7 +150,7 @@ pub trait Backend<T: Config>: sealed::Sealed + Send + Sync + 'static {
method: &str,
call_parameters: Option<&[u8]>,
at: HashFor<T>,
) -> Result<Vec<u8>, Error>;
) -> Result<Vec<u8>, BackendError>;
}
/// helpful utility methods derived from those provided on [`Backend`]
@@ -160,7 +161,7 @@ pub trait BackendExt<T: Config>: Backend<T> {
&self,
key: Vec<u8>,
at: HashFor<T>,
) -> Result<Option<Vec<u8>>, Error> {
) -> Result<Option<Vec<u8>>, BackendError> {
self.storage_fetch_values(vec![key], at)
.await?
.next()
@@ -176,32 +177,39 @@ pub trait BackendExt<T: Config>: Backend<T> {
method: &str,
call_parameters: Option<&[u8]>,
at: HashFor<T>,
) -> Result<D, Error> {
) -> Result<D, BackendError> {
let bytes = self.call(method, call_parameters, at).await?;
let res = D::decode(&mut &*bytes)?;
let res =
D::decode(&mut &*bytes).map_err(BackendError::CouldNotScaleDecodeRuntimeResponse)?;
Ok(res)
}
/// Return the metadata at some version.
async fn metadata_at_version(&self, version: u32, at: HashFor<T>) -> Result<Metadata, Error> {
async fn metadata_at_version(
&self,
version: u32,
at: HashFor<T>,
) -> Result<Metadata, BackendError> {
let param = version.encode();
let opaque: Option<frame_metadata::OpaqueMetadata> = self
.call_decoding("Metadata_metadata_at_version", Some(&param), at)
.await?;
let Some(opaque) = opaque else {
return Err(Error::Other("Metadata version not found".into()));
return Err(BackendError::MetadataVersionNotFound(version));
};
let metadata: Metadata = Decode::decode(&mut &opaque.0[..])?;
let metadata: Metadata =
Decode::decode(&mut &opaque.0[..]).map_err(BackendError::CouldNotDecodeMetadata)?;
Ok(metadata)
}
/// Return V14 metadata from the legacy `Metadata_metadata` call.
async fn legacy_metadata(&self, at: HashFor<T>) -> Result<Metadata, Error> {
async fn legacy_metadata(&self, at: HashFor<T>) -> Result<Metadata, BackendError> {
let opaque: frame_metadata::OpaqueMetadata =
self.call_decoding("Metadata_metadata", None, at).await?;
let metadata: Metadata = Decode::decode(&mut &opaque.0[..])?;
let metadata: Metadata =
Decode::decode(&mut &opaque.0[..]).map_err(BackendError::CouldNotDecodeMetadata)?;
Ok(metadata)
}
}
@@ -325,8 +333,8 @@ impl<T> StreamOf<T> {
}
}
/// A stream of [`Result<Item, Error>`].
pub type StreamOfResults<T> = StreamOf<Result<T, Error>>;
/// A stream of [`Result<Item, BackendError>`].
pub type StreamOfResults<T> = StreamOf<Result<T, BackendError>>;
/// The status of the transaction.
///
@@ -541,7 +549,7 @@ mod test {
/// - `call`
/// The test covers them because they follow the simple pattern of:
/// ```rust,no_run,standalone_crate
/// async fn THE_THING(&self) -> Result<HashFor<T>, Error> {
/// async fn THE_THING(&self) -> Result<HashFor<T>, BackendError> {
/// retry(|| <DO THE THING> ).await
/// }
/// ```
@@ -574,7 +582,7 @@ mod test {
/// ```rust,no_run,standalone_crate
/// async fn stream_the_thing(
/// &self,
/// ) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, Error> {
/// ) -> Result<StreamOfResults<(T::Header, BlockRef<HashFor<T>>)>, BackendError> {
/// let methods = self.methods.clone();
/// let retry_sub = retry_stream(move || {
/// let methods = methods.clone();
@@ -635,7 +643,7 @@ mod test {
);
assert!(matches!(
results.next().await.unwrap(),
Err(Error::Rpc(RpcError::ClientError(
Err(BackendError::Rpc(RpcError::ClientError(
subxt_rpcs::Error::Client(_)
)))
));
@@ -644,7 +652,6 @@ mod test {
}
mod unstable_backend {
use crate::error::RpcError;
use subxt_rpcs::methods::chain_head::{
self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError,
OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent,
@@ -858,7 +865,7 @@ mod test {
.next()
.await
.unwrap()
.is_err_and(|e| matches!(e, Error::Other(e) if e == "error"))
.is_err_and(|e| matches!(e, BackendError::Other(e) if e == "error"))
);
assert!(response.next().await.is_none());
}
@@ -1047,7 +1054,7 @@ mod test {
let response = backend
.storage_fetch_values(["ID1".into()].into(), random_hash())
.await;
assert!(matches!(response, Err(Error::Rpc(RpcError::LimitReached))));
assert!(matches!(response, Err(e) if e.is_rpc_limit_reached()));
// Advance the driver until a new chainHead_follow subscription has been started up.
let _ = driver.next().await.unwrap();
+15 -14
View File
@@ -1,7 +1,7 @@
//! RPC utils.
use super::{StreamOf, StreamOfResults};
use crate::error::Error;
use crate::error::BackendError;
use futures::future::BoxFuture;
use futures::{FutureExt, Stream, StreamExt};
use std::{future::Future, pin::Pin, task::Poll};
@@ -10,10 +10,11 @@ use std::{future::Future, pin::Pin, task::Poll};
type ResubscribeGetter<T> = Box<dyn FnMut() -> ResubscribeFuture<T> + Send>;
/// Future that resolves to a subscription stream.
type ResubscribeFuture<T> = Pin<Box<dyn Future<Output = Result<StreamOfResults<T>, Error>> + Send>>;
type ResubscribeFuture<T> =
Pin<Box<dyn Future<Output = Result<StreamOfResults<T>, BackendError>> + Send>>;
pub(crate) enum PendingOrStream<T> {
Pending(BoxFuture<'static, Result<StreamOfResults<T>, Error>>),
Pending(BoxFuture<'static, Result<StreamOfResults<T>, BackendError>>),
Stream(StreamOfResults<T>),
}
@@ -35,7 +36,7 @@ struct RetrySubscription<T> {
impl<T> std::marker::Unpin for RetrySubscription<T> {}
impl<T> Stream for RetrySubscription<T> {
type Item = Result<T, Error>;
type Item = Result<T, BackendError>;
fn poll_next(
mut self: Pin<&mut Self>,
@@ -92,7 +93,7 @@ impl<T> Stream for RetrySubscription<T> {
/// ```rust,no_run,standalone_crate
/// use subxt::backend::utils::retry;
///
/// async fn some_future() -> Result<(), subxt::error::Error> {
/// async fn some_future() -> Result<(), subxt::error::BackendError> {
/// Ok(())
/// }
///
@@ -101,10 +102,10 @@ impl<T> Stream for RetrySubscription<T> {
/// let result = retry(|| some_future()).await;
/// }
/// ```
pub async fn retry<T, F, R>(mut retry_future: F) -> Result<R, Error>
pub async fn retry<T, F, R>(mut retry_future: F) -> Result<R, BackendError>
where
F: FnMut() -> T,
T: Future<Output = Result<R, Error>>,
T: Future<Output = Result<R, BackendError>>,
{
const REJECTED_MAX_RETRIES: usize = 10;
let mut rejected_retries = 0;
@@ -163,7 +164,7 @@ where
/// }).await;
/// }
/// ```
pub async fn retry_stream<F, R>(sub_stream: F) -> Result<StreamOfResults<R>, Error>
pub async fn retry_stream<F, R>(sub_stream: F) -> Result<StreamOfResults<R>, BackendError>
where
F: FnMut() -> ResubscribeFuture<R> + Send + 'static + Clone,
R: Send + 'static,
@@ -187,12 +188,12 @@ mod tests {
use super::*;
use crate::backend::StreamOf;
fn disconnect_err() -> Error {
Error::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into())
fn disconnect_err() -> BackendError {
BackendError::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into())
}
fn custom_err() -> Error {
Error::Other(String::new())
fn custom_err() -> BackendError {
BackendError::Other(String::new())
}
#[tokio::test]
@@ -213,7 +214,7 @@ mod tests {
let result = retry_stream
.take(5)
.collect::<Vec<Result<usize, Error>>>()
.collect::<Vec<Result<usize, BackendError>>>()
.await;
assert!(matches!(result[0], Ok(r) if r == 1));
@@ -270,6 +271,6 @@ mod tests {
assert!(matches!(result[0], Ok(r) if r == 1));
assert!(matches!(result[1], Err(ref e) if e.is_disconnected_will_reconnect()));
assert!(matches!(result[2], Err(ref e) if matches!(e, Error::Other(_))));
assert!(matches!(result[2], Err(ref e) if matches!(e, BackendError::Other(_))));
}
}
+33 -21
View File
@@ -7,10 +7,10 @@ use crate::{
blocks::Extrinsics,
client::{OfflineClientT, OnlineClientT},
config::{Config, HashFor, Header},
error::{BlockError, DecodeError, Error},
error::{AccountNonceError, BlockError, EventsError, ExtrinsicError},
events,
runtime_api::RuntimeApi,
storage::Storage,
storage::StorageClientAt,
};
use codec::{Decode, Encode};
@@ -84,38 +84,51 @@ where
C: OnlineClientT<T>,
{
/// Return the events associated with the block, fetching them from the node if necessary.
pub async fn events(&self) -> Result<events::Events<T>, Error> {
pub async fn events(&self) -> Result<events::Events<T>, EventsError> {
get_events(&self.client, self.hash(), &self.cached_events).await
}
/// Fetch and return the extrinsics in the block body.
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, Error> {
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, ExtrinsicError> {
let block_hash = self.hash();
let Some(extrinsics) = self.client.backend().block_body(block_hash).await? else {
return Err(BlockError::not_found(block_hash).into());
};
Extrinsics::new(
let extrinsics = self
.client
.backend()
.block_body(block_hash)
.await
.map_err(ExtrinsicError::CannotGetBlockBody)?
.ok_or_else(|| ExtrinsicError::BlockNotFound(block_hash.into()))?;
let extrinsics = Extrinsics::new(
self.client.clone(),
extrinsics,
self.cached_events.clone(),
block_hash,
)
)?;
Ok(extrinsics)
}
/// Work with storage.
pub fn storage(&self) -> Storage<T, C> {
Storage::new(self.client.clone(), self.block_ref.clone())
pub fn storage(&self) -> StorageClientAt<T, C> {
StorageClientAt::new(self.client.clone(), self.block_ref.clone())
}
/// Execute a runtime API call at this block.
pub async fn runtime_api(&self) -> Result<RuntimeApi<T, C>, Error> {
Ok(RuntimeApi::new(self.client.clone(), self.block_ref.clone()))
pub async fn runtime_api(&self) -> RuntimeApi<T, C> {
RuntimeApi::new(self.client.clone(), self.block_ref.clone())
}
/// Get the account nonce for a given account ID at this block.
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, Error> {
get_account_nonce(&self.client, account_id, self.hash()).await
pub async fn account_nonce(&self, account_id: &T::AccountId) -> Result<u64, BlockError> {
get_account_nonce(&self.client, account_id, self.hash())
.await
.map_err(|e| BlockError::AccountNonceError {
block_hash: self.hash().into(),
account_id: account_id.encode().into(),
reason: e,
})
}
}
@@ -124,7 +137,7 @@ pub(crate) async fn get_events<C, T>(
client: &C,
block_hash: HashFor<T>,
cached_events: &AsyncMutex<Option<events::Events<T>>>,
) -> Result<events::Events<T>, Error>
) -> Result<events::Events<T>, EventsError>
where
T: Config,
C: OnlineClientT<T>,
@@ -152,7 +165,7 @@ pub(crate) async fn get_account_nonce<C, T>(
client: &C,
account_id: &T::AccountId,
block_hash: HashFor<T>,
) -> Result<u64, Error>
) -> Result<u64, AccountNonceError>
where
C: OnlineClientT<T>,
T: Config,
@@ -173,10 +186,9 @@ where
4 => u32::decode(cursor)?.into(),
8 => u64::decode(cursor)?,
_ => {
return Err(Error::Decode(DecodeError::custom_string(format!(
"state call AccountNonceApi_account_nonce returned an unexpected number of bytes: {} (expected 2, 4 or 8)",
account_nonce_bytes.len()
))));
return Err(AccountNonceError::WrongNumberOfBytes(
account_nonce_bytes.len(),
));
}
};
Ok(account_nonce)
+42 -16
View File
@@ -7,7 +7,7 @@ use crate::{
backend::{BlockRef, StreamOfResults},
client::OnlineClientT,
config::{Config, HashFor},
error::{BlockError, Error},
error::BlockError,
utils::PhantomDataSendSync,
};
use derive_where::derive_where;
@@ -15,7 +15,7 @@ use futures::StreamExt;
use std::future::Future;
type BlockStream<T> = StreamOfResults<T>;
type BlockStreamRes<T> = Result<BlockStream<T>, Error>;
type BlockStreamRes<T> = Result<BlockStream<T>, BlockError>;
/// A client for working with blocks.
#[derive_where(Clone; Client)]
@@ -49,14 +49,14 @@ where
pub fn at(
&self,
block_ref: impl Into<BlockRef<HashFor<T>>>,
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
self.at_or_latest(Some(block_ref.into()))
}
/// Obtain block details of the latest finalized block.
pub fn at_latest(
&self,
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
self.at_or_latest(None)
}
@@ -65,18 +65,35 @@ where
fn at_or_latest(
&self,
block_ref: Option<BlockRef<HashFor<T>>>,
) -> impl Future<Output = Result<Block<T, Client>, Error>> + Send + 'static {
) -> impl Future<Output = Result<Block<T, Client>, BlockError>> + Send + 'static {
let client = self.client.clone();
async move {
// If a block ref isn't provided, we'll get the latest finalized ref to use.
let block_ref = match block_ref {
Some(r) => r,
None => client.backend().latest_finalized_block_ref().await?,
None => client
.backend()
.latest_finalized_block_ref()
.await
.map_err(BlockError::CouldNotGetLatestBlock)?,
};
let block_header = match client.backend().block_header(block_ref.hash()).await? {
let maybe_block_header = client
.backend()
.block_header(block_ref.hash())
.await
.map_err(|e| BlockError::CouldNotGetBlockHeader {
block_hash: block_ref.hash().into(),
reason: e,
})?;
let block_header = match maybe_block_header {
Some(header) => header,
None => return Err(BlockError::not_found(block_ref.hash()).into()),
None => {
return Err(BlockError::BlockNotFound {
block_hash: block_ref.hash().into(),
});
}
};
Ok(Block::new(block_header, block_ref, client))
@@ -89,14 +106,18 @@ where
/// the time.
pub fn subscribe_all(
&self,
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
where
Client: Send + Sync + 'static,
{
let client = self.client.clone();
let hasher = client.hasher();
header_sub_fut_to_block_sub(self.clone(), async move {
let stream = client.backend().stream_all_block_headers(hasher).await?;
let stream = client
.backend()
.stream_all_block_headers(hasher)
.await
.map_err(BlockError::CouldNotSubscribeToAllBlocks)?;
BlockStreamRes::Ok(stream)
})
}
@@ -107,14 +128,18 @@ where
/// the time.
pub fn subscribe_best(
&self,
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
where
Client: Send + Sync + 'static,
{
let client = self.client.clone();
let hasher = client.hasher();
header_sub_fut_to_block_sub(self.clone(), async move {
let stream = client.backend().stream_best_block_headers(hasher).await?;
let stream = client
.backend()
.stream_best_block_headers(hasher)
.await
.map_err(BlockError::CouldNotSubscribeToBestBlocks)?;
BlockStreamRes::Ok(stream)
})
}
@@ -122,7 +147,7 @@ where
/// Subscribe to finalized blocks.
pub fn subscribe_finalized(
&self,
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, Error>> + Send + 'static
) -> impl Future<Output = Result<BlockStream<Block<T, Client>>, BlockError>> + Send + 'static
where
Client: Send + Sync + 'static,
{
@@ -132,7 +157,8 @@ where
let stream = client
.backend()
.stream_finalized_block_headers(hasher)
.await?;
.await
.map_err(BlockError::CouldNotSubscribeToFinalizedBlocks)?;
BlockStreamRes::Ok(stream)
})
}
@@ -143,10 +169,10 @@ where
async fn header_sub_fut_to_block_sub<T, Client, S>(
blocks_client: BlocksClient<T, Client>,
sub: S,
) -> Result<BlockStream<Block<T, Client>>, Error>
) -> Result<BlockStream<Block<T, Client>>, BlockError>
where
T: Config,
S: Future<Output = Result<BlockStream<(T::Header, BlockRef<HashFor<T>>)>, Error>>
S: Future<Output = Result<BlockStream<(T::Header, BlockRef<HashFor<T>>)>, BlockError>>
+ Send
+ 'static,
Client: OnlineClientT<T> + Send + Sync + 'static,
+32 -35
View File
@@ -6,18 +6,16 @@ use crate::{
blocks::block_types::{CachedEvents, get_events},
client::{OfflineClientT, OnlineClientT},
config::{Config, HashFor},
error::Error,
error::{EventsError, ExtrinsicDecodeErrorAt, ExtrinsicError},
events,
};
use derive_where::derive_where;
use scale_decode::DecodeAsType;
use scale_decode::{DecodeAsFields, DecodeAsType};
use subxt_core::blocks::{ExtrinsicDetails as CoreExtrinsicDetails, Extrinsics as CoreExtrinsics};
// Re-export anything that's directly returned/used in the APIs below.
pub use subxt_core::blocks::{
ExtrinsicMetadataDetails, ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
StaticExtrinsic,
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions, StaticExtrinsic,
};
/// The body of a block.
@@ -38,7 +36,7 @@ where
extrinsics: Vec<Vec<u8>>,
cached_events: CachedEvents<T>,
hash: HashFor<T>,
) -> Result<Self, Error> {
) -> Result<Self, ExtrinsicDecodeErrorAt> {
let inner = CoreExtrinsics::decode_from(extrinsics, client.metadata())?;
Ok(Self {
inner,
@@ -81,10 +79,10 @@ where
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<E: StaticExtrinsic>(
&self,
) -> impl Iterator<Item = Result<FoundExtrinsic<T, C, E>, Error>> {
) -> impl Iterator<Item = Result<FoundExtrinsic<T, C, E>, ExtrinsicError>> {
self.inner.find::<E>().map(|res| {
match res {
Err(e) => Err(Error::from(e)),
Err(e) => Err(ExtrinsicError::from(e)),
Ok(ext) => {
// Wrap details from subxt-core into what we want here:
let details = ExtrinsicDetails::new(
@@ -105,18 +103,22 @@ where
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the first extrinsic found which decodes to the provided `E` type.
pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, C, E>>, Error> {
pub fn find_first<E: StaticExtrinsic>(
&self,
) -> Result<Option<FoundExtrinsic<T, C, E>>, ExtrinsicError> {
self.find::<E>().next().transpose()
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, C, E>>, Error> {
pub fn find_last<E: StaticExtrinsic>(
&self,
) -> Result<Option<FoundExtrinsic<T, C, E>>, ExtrinsicError> {
self.find::<E>().last().transpose()
}
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, ExtrinsicError> {
Ok(self.find::<E>().next().transpose()?.is_some())
}
}
@@ -207,38 +209,33 @@ where
self.inner.pallet_index()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::variant_index()`].
pub fn variant_index(&self) -> u8 {
self.inner.variant_index()
/// See [`subxt_core::blocks::ExtrinsicDetails::call_index()`].
pub fn call_index(&self) -> u8 {
self.inner.call_index()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::pallet_name()`].
pub fn pallet_name(&self) -> Result<&str, Error> {
self.inner.pallet_name().map_err(Into::into)
pub fn pallet_name(&self) -> &str {
self.inner.pallet_name()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::variant_name()`].
pub fn variant_name(&self) -> Result<&str, Error> {
self.inner.variant_name().map_err(Into::into)
/// See [`subxt_core::blocks::ExtrinsicDetails::call_name()`].
pub fn call_name(&self) -> &str {
self.inner.call_name()
}
/// See [`subxt_core::blocks::ExtrinsicDetails::extrinsic_metadata()`].
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
self.inner.extrinsic_metadata().map_err(Into::into)
}
/// See [`subxt_core::blocks::ExtrinsicDetails::field_values()`].
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
self.inner.field_values().map_err(Into::into)
/// See [`subxt_core::blocks::ExtrinsicDetails::decode_as_fields()`].
pub fn decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
self.inner.decode_as_fields().map_err(Into::into)
}
/// See [`subxt_core::blocks::ExtrinsicDetails::as_extrinsic()`].
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, ExtrinsicError> {
self.inner.as_extrinsic::<E>().map_err(Into::into)
}
/// See [`subxt_core::blocks::ExtrinsicDetails::as_root_extrinsic()`].
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, ExtrinsicError> {
self.inner.as_root_extrinsic::<E>().map_err(Into::into)
}
}
@@ -249,7 +246,7 @@ where
C: OnlineClientT<T>,
{
/// The events associated with the extrinsic.
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, Error> {
pub async fn events(&self) -> Result<ExtrinsicEvents<T>, EventsError> {
let events = get_events(&self.client, self.block_hash, &self.cached_events).await?;
let ext_hash = self.inner.hash();
Ok(ExtrinsicEvents::new(ext_hash, self.index(), events))
@@ -308,7 +305,7 @@ impl<T: Config> ExtrinsicEvents<T> {
///
/// This works in the same way that [`events::Events::iter()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, Error>> {
pub fn iter(&self) -> impl Iterator<Item = Result<events::EventDetails<T>, EventsError>> {
self.events.iter().filter(|ev| {
ev.as_ref()
.map(|ev| ev.phase() == events::Phase::ApplyExtrinsic(self.idx))
@@ -320,7 +317,7 @@ impl<T: Config> ExtrinsicEvents<T> {
///
/// This works in the same way that [`events::Events::find()`] does, with the
/// exception that it filters out events not related to the submitted extrinsic.
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, Error>> {
pub fn find<Ev: events::StaticEvent>(&self) -> impl Iterator<Item = Result<Ev, EventsError>> {
self.iter()
.filter_map(|ev| ev.and_then(|ev| ev.as_event::<Ev>()).transpose())
}
@@ -330,7 +327,7 @@ impl<T: Config> ExtrinsicEvents<T> {
///
/// This works in the same way that [`events::Events::find_first()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_first<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().next().transpose()
}
@@ -339,7 +336,7 @@ impl<T: Config> ExtrinsicEvents<T> {
///
/// This works in the same way that [`events::Events::find_last()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, Error> {
pub fn find_last<Ev: events::StaticEvent>(&self) -> Result<Option<Ev>, EventsError> {
self.find::<Ev>().last().transpose()
}
@@ -347,7 +344,7 @@ impl<T: Config> ExtrinsicEvents<T> {
///
/// This works in the same way that [`events::Events::has()`] does, with the
/// exception that it ignores events not related to the submitted extrinsic.
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, Error> {
pub fn has<Ev: events::StaticEvent>(&self) -> Result<bool, EventsError> {
Ok(self.find::<Ev>().next().transpose()?.is_some())
}
}
+21 -17
View File
@@ -12,11 +12,11 @@
//! - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()),
//! [best](crate::blocks::BlocksClient::subscribe_best()) or
//! [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced.
//! Prefer to subscribe to finalized blocks unless you know what you're doing.
//! **Prefer to subscribe to finalized blocks unless you know what you're doing.**
//!
//! In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various
//! information about the block, such a the [header](crate::blocks::Block::header()), [block
//! number](crate::blocks::Block::number()) and [body (the extrinsics)](crate::blocks::Block::extrinsics()).
//! information about the block, such a the [header](crate::blocks::Block::header()),
//! [block number](crate::blocks::Block::number()) and [body (the extrinsics)](crate::blocks::Block::extrinsics()).
//! [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the
//! given block:
//!
@@ -29,8 +29,8 @@
//!
//! ## Decoding Extrinsics
//!
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and [iterate over
//! the extrinsics](crate::blocks::Extrinsics::iter()) stored within it. The extrinsics yielded are of type
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and
//! [iterate over the extrinsics](crate::blocks::Extrinsics::iter) stored within it. The extrinsics yielded are of type
//! [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), which is just a blob of bytes that also stores which
//! pallet and call in that pallet it belongs to. It also contains information about signed extensions that
//! have been used for submitting this extrinsic.
@@ -64,8 +64,8 @@
//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last).
//!
//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce())
//! transaction extensions.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) transaction extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/block_decoding_static.rs")]
@@ -73,11 +73,13 @@
//!
//! ### Dynamically decode the extrinsic
//!
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated
//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()),
//! which gives you access to it's fields as a [scale value composite](scale_value::Composite).
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and transaction extensions dynamically.
//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not
//! have access to a statically generated interface module that contains the relevant Rust types. You can
//! [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::decode_as_fields()), which gives
//! you access to it's fields as a [scale value composite](scale_value::Composite). The following example
//! looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data
//! fields and transaction extensions dynamically. Notice how we do not need to use code generation via the
//! subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
//! Other than that it works in a chain-agnostic way:
//!
//! ```rust,ignore
@@ -90,10 +92,12 @@
//! The [Config](crate::Config) implementation for your chain defines which transaction extensions you expect.
//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in,
//! you can try to [get its transaction extensions](crate::blocks::ExtrinsicDetails::transaction_extensions()).
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find),
//! in the returned [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
//! These are only available on V4 signed extrinsics or V5 general extrinsics. You can try to
//! [find a specific transaction extension](crate::blocks::ExtrinsicTransactionExtensions::find), in the returned
//! [transaction extensions](crate::blocks::ExtrinsicTransactionExtensions).
//!
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and the
//! [account nonce](crate::blocks::ExtrinsicTransactionExtensions::tip()) associated with an extrinsic, given its transaction extensions.
//! If you prefer to do things dynamically you can get the data of the transaction extension as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicTransactionExtensions::tip()) and
//! the [account nonce](crate::blocks::ExtrinsicTransactionExtensions::nonce()) associated with an extrinsic, given
//! its transaction extensions. If you prefer to do things dynamically you can get the data of the transaction extension
//! as a [scale value](crate::blocks::ExtrinsicTransactionExtension::value()).
//!
+8 -9
View File
@@ -22,23 +22,22 @@
//! let constant_query = polkadot::constants().system().block_length();
//! ```
//!
//! Alternately, we can dynamically construct a constant query:
//! Alternately, we can dynamically construct a constant query. A dynamic query needs the return
//! type to be specified, where we can use [`crate::dynamic::Value`] if unsure:
//!
//! ```rust,no_run,standalone_crate
//! use subxt::dynamic::Value;
//!
//! let storage_query = subxt::dynamic::constant("System", "BlockLength");
//! let storage_query = subxt::dynamic::constant::<Value>("System", "BlockLength");
//! ```
//!
//! Static queries also have a static return type, so the constant is decoded appropriately. In
//! addition, they are validated at runtime to ensure that they align with the current node state.
//! Dynamic queries must be decoded into some static type manually, or into the dynamic
//! [`crate::dynamic::Value`] type.
//!
//! ## Submitting it
//!
//! Constant queries are handed to Subxt via [`crate::constants::ConstantsClient::at()`]. It's worth
//! noting that constant values are pulled directly out of the node metadata which Subxt has
//! Call [`crate::constants::ConstantsClient::at()`] to return and decode the constant into the
//! type given by the address, or [`crate::constants::ConstantsClient::bytes_at()`] to return the
//! raw bytes for some constant.
//!
//! Constant values are pulled directly out of the node metadata which Subxt has
//! already acquired, and so this function requires no network access and is available from a
//! [`crate::OfflineClient`].
//!
+26 -11
View File
@@ -13,13 +13,32 @@
//!
//! ## Getting a custom value
//!
//! Custom values can be accessed via a [`CustomValuesClient`](crate::custom_values::CustomValuesClient).
//! The client exposes an `at` function by which a custom value can be fetched, given an address to this custom value.
//! An address can be as simple as the aforementioned __name__ as a [str]. This will return a dynamic value, that you can manually decode into the type you want.
//! Suppose, the custom types contain a value of type `Foo` under the name `"foo"` you can access it like in this example:
//! First, you must construct an address to access a custom value. This can be either:
//! - a raw [`str`] which assumes the return type to be the dynamic [`crate::dynamic::Value`] type,
//! - created via [`dynamic`](crate::custom_values::dynamic) function whereby you set the return type
//! that you want back,
//! - created via statically generated addresses as part of the `#[subxt]` macro which define the return type.
//!
//! With an address, use [`at`](crate::custom_values::CustomValuesClient::at) to access and decode specific values, and
//! [`bytes_at`](crate::custom_values::CustomValuesClient::bytes_at) to access the raw bytes.
//!
//! ## Examples
//!
//! Dynamically accessing a custom value using a [`str`] to select which one:
//!
//! ```rust,ignore
//! use subxt::{OnlineClient, PolkadotConfig, ext::{codec::Decode, scale_decode::DecodeAsType}};
//! use subxt::{OnlineClient, PolkadotConfig, ext::scale_decode::DecodeAsType};
//! use subxt::dynamic::Value;
//!
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
//! let custom_value_client = api.custom_values();
//! let foo: Value = custom_value_client.at("foo")?;
//! ```
//!
//! Use the [`dynamic`](crate::custom_values::dynamic) function to select the return type:
//!
//! ```rust,ignore
//! use subxt::{OnlineClient, PolkadotConfig, ext::scale_decode::DecodeAsType};
//!
//! #[derive(Decode, DecodeAsType, Debug)]
//! struct Foo {
@@ -29,9 +48,8 @@
//!
//! let api = OnlineClient::<PolkadotConfig>::new().await?;
//! let custom_value_client = api.custom_values();
//! let foo_dynamic = custom_value_client.at("foo")?;
//! let foo: Foo = foo_dynamic.as_type()?;
//!
//! let custom_value_addr = subxt::custom_values::dynamic::<Foo>("foo");
//! let foo: Foo = custom_value_client.at(&custom_value_addr)?;
//! ```
//!
//! Alternatively we also provide a statically generated api for custom values:
@@ -49,6 +67,3 @@
//! let foo = custom_value_client.at(&static_address)?;
//! ```
//!
//! Note: Names of custom values are converted to __snake_case__ to produce a valid function name during code generation.
//! If there are multiple values where the names would be equal when converted to __snake_case__, functions might not be statically generated for some of them, because of naming conflicts.
//! Make sure names in the custom values of your metadata differ significantly.
+5 -3
View File
@@ -28,15 +28,17 @@
//! let runtime_call = polkadot::apis().metadata().metadata_versions();
//! ```
//!
//! Alternately, we can dynamically construct a runtime call:
//! Alternately, we can dynamically construct a runtime call. The input type can be a tuple or
//! vec or valid types implementing [`scale_encode::EncodeAsType`], and the output can be anything
//! implementing [`scale_decode::DecodeAsType`]:
//!
//! ```rust,no_run
//! use subxt::dynamic::Value;
//!
//! let runtime_call = subxt::dynamic::runtime_api_call(
//! let runtime_call = subxt::dynamic::runtime_api_call::<(), Vec<u32>>(
//! "Metadata",
//! "metadata_versions",
//! Vec::<Value<()>>::new()
//! ()
//! );
//! ```
//!
+22 -70
View File
@@ -9,90 +9,57 @@
//! node storage. With Subxt, you can query this key/value storage with the following steps:
//!
//! 1. [Constructing a storage query](#constructing-a-storage-query).
//! 2. [Submitting the query to get back the associated values](#submitting-it).
//! 2. [Submitting the query to get back the associated entry](#submitting-it).
//! 3. [Fetching](#fetching-storage-entries) or [iterating](#iterating-storage-entries) over that
//! entry to retrieve the value or values within it.
//!
//! ## Constructing a storage query
//!
//! We can use the statically generated interface to build storage queries:
//!
//! ```rust,no_run,standalone_crate
//! use subxt_signer::sr25519::dev;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
//! pub mod polkadot {}
//!
//! let account = dev::alice().public_key().into();
//! let storage_query = polkadot::storage().system().account(account);
//! let storage_query = polkadot::storage().system().account();
//! ```
//!
//! Alternately, we can dynamically construct a storage query. This will not be type checked or
//! validated until it's submitted:
//! Alternately, we can dynamically construct a storage query. A dynamic query needs the input
//! and return value types to be specified, where we can use [`crate::dynamic::Value`] if unsure.
//!
//! ```rust,no_run,standalone_crate
//! use subxt_signer::sr25519::dev;
//! use subxt::dynamic::Value;
//!
//! let account = dev::alice().public_key();
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
//! Value::from_bytes(account)
//! ]);
//! let storage_query = subxt::dynamic::storage::<(Value,), Value>("System", "Account");
//! ```
//!
//! As well as accessing specific entries, some storage locations can also be iterated over (such as
//! the map of account information). To do this, suffix `_iter` onto the query constructor (this
//! will only be available on static constructors when iteration is actually possible):
//!
//! ```rust,no_run,standalone_crate
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
//! pub mod polkadot {}
//!
//! // A static query capable of iterating over accounts:
//! let storage_query = polkadot::storage().system().account_iter();
//! // A dynamic query to do the same:
//! let storage_query = subxt::dynamic::storage("System", "Account", ());
//! ```
//!
//! Some storage entries are maps with multiple keys. As an example, we might end up with
//! an API like `runtime::storage().foo().bar(u8, bool, u16, String)` to fetch some entry "bar".
//! When this is the case, the codegen will generate multiple iterator query functions alongside
//! the function to fetch an individual value:
//!
//! - `runtime::storage().foo().bar(u8, bool, u16, String)`: fetch a single entry from the "bar" map.
//! - `runtime::storage().foo().bar_iter()`: iterate over all of the entries in the "bar" map.
//! - `runtime::storage().foo().bar_iter1(u8)`: iterate over all of the entries in the "bar" map under
//! a given `u8`.
//! - `runtime::storage().foo().bar_iter2(u8, bool)`: iterate over all of the entries in the "bar" map under
//! a given `u8` and `bool` value.
//! - `runtime::storage().foo().bar_iter3(u8, bool, u16)`: iterate over all of the entries in the "bar" map under
//! a given `u8`, `bool` and `u16` value.
//!
//! All valid storage queries implement [`crate::storage::Address`]. As well as describing
//! how to build a valid storage query, this trait also has some associated types that determine the
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
//! over storage entries using it).
//!
//! Static queries set appropriate values for these associated types, and can therefore only be used
//! where it makes sense. Dynamic queries don't know any better and can be used in more places, but
//! may fail at runtime instead if they are invalid in those places.
//!
//! ## Submitting it
//!
//! Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to
//! Storage queries can be handed to various functions in [`crate::storage::StorageClientAt`] in order to
//! obtain the associated values (also referred to as storage entries) back.
//!
//! The core API here is [`crate::storage::StorageClientAt::entry()`], which takes a query and looks up the
//! corresponding storage entry, from which you can then fetch or iterate over the values contained within.
//! [`crate::storage::StorageClientAt::fetch()`] and [`crate::storage::StorageClientAt::iter()`] are shorthand
//! for this.
//!
//! When you wish to manually query some entry, [`crate::storage::StorageClientAt::fetch_raw()`] exists to take
//! in raw bytes pointing at some storage value, and return the value bytes if possible. [`crate::storage::StorageClientAt::storage_version()`]
//! and [`crate::storage::StorageClientAt::runtime_wasm_code()`] use this to retrieve the version of some storage API
//! and the current Runtime WASM blob respectively.
//!
//! ### Fetching storage entries
//!
//! The simplest way to access storage entries is to construct a query and then call either
//! [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the
//! latter will only work for storage queries that have a default value when empty):
//! [`crate::storage::StorageClientAt::fetch()`]:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/storage_fetch.rs")]
//! ```
//!
//! For completeness, below is an example using a dynamic query instead. The return type from a
//! dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a
//! [`crate::dynamic::Value`], or else the raw bytes can be accessed instead.
//! For completeness, below is an example using a dynamic query instead. Dynamic queries can define the types that
//! they wish to accept inputs and decode the return value into ([`crate::dynamic::Value`] can be used here anywhere we
//! are not sure of the specific types).
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/storage_fetch_dynamic.rs")]
@@ -113,18 +80,3 @@
#![doc = include_str!("../../../examples/storage_iterating_dynamic.rs")]
//! ```
//!
//! Here is an example of iterating over partial keys. In this example some multi-signature operations
//! are sent to the node. We can iterate over the pending multisig operations of a single multisig account:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/storage_iterating_partial.rs")]
//! ```
//!
//! ### Advanced
//!
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
//! [`crate::storage::Storage::fetch_raw_keys`]. Both of these take raw bytes as arguments, which can be
//! obtained from a [`crate::storage::Address`] by using
//! [`crate::storage::StorageClient::address_bytes()`] or
//! [`crate::storage::StorageClient::address_root_bytes()`].
//!
+1 -1
View File
@@ -13,6 +13,6 @@ mod online_client;
pub use offline_client::{OfflineClient, OfflineClientT};
pub use online_client::{
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError,
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update,
};
pub use subxt_core::client::{ClientState, RuntimeVersion};
+82 -87
View File
@@ -10,7 +10,7 @@ use crate::{
blocks::{BlockRef, BlocksClient},
config::{Config, HashFor},
constants::ConstantsClient,
error::Error,
error::{BackendError, OnlineClientError, RuntimeUpdateeApplyError, RuntimeUpdaterError},
events::EventsClient,
runtime_api::RuntimeApiClient,
storage::StorageClient,
@@ -18,6 +18,7 @@ use crate::{
view_functions::ViewFunctionsClient,
};
use derive_where::derive_where;
use futures::TryFutureExt;
use futures::future;
use std::sync::{Arc, RwLock};
use subxt_core::client::{ClientState, RuntimeVersion};
@@ -60,13 +61,13 @@ impl<T: Config> std::fmt::Debug for OnlineClient<T> {
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] using default settings which
/// point to a locally running node on `ws://127.0.0.1:9944`.
pub async fn new() -> Result<OnlineClient<T>, Error> {
pub async fn new() -> Result<OnlineClient<T>, OnlineClientError> {
let url = "ws://127.0.0.1:9944";
OnlineClient::from_url(url).await
}
/// Construct a new [`OnlineClient`], providing a URL to connect to.
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
pub async fn from_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, OnlineClientError> {
subxt_rpcs::utils::validate_url_is_secure(url.as_ref())?;
OnlineClient::from_insecure_url(url).await
}
@@ -74,7 +75,9 @@ impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`], providing a URL to connect to.
///
/// Allows insecure URLs without SSL encryption, e.g. (http:// and ws:// URLs).
pub async fn from_insecure_url(url: impl AsRef<str>) -> Result<OnlineClient<T>, Error> {
pub async fn from_insecure_url(
url: impl AsRef<str>,
) -> Result<OnlineClient<T>, OnlineClientError> {
let client = RpcClient::from_insecure_url(url).await?;
let backend = LegacyBackend::builder().build(client);
OnlineClient::from_backend(Arc::new(backend)).await
@@ -86,7 +89,7 @@ impl<T: Config> OnlineClient<T> {
/// This will use the current default [`Backend`], which may change in future releases.
pub async fn from_rpc_client(
rpc_client: impl Into<RpcClient>,
) -> Result<OnlineClient<T>, Error> {
) -> Result<OnlineClient<T>, OnlineClientError> {
let rpc_client = rpc_client.into();
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
OnlineClient::from_backend(backend).await
@@ -110,7 +113,7 @@ impl<T: Config> OnlineClient<T> {
runtime_version: RuntimeVersion,
metadata: impl Into<Metadata>,
rpc_client: impl Into<RpcClient>,
) -> Result<OnlineClient<T>, Error> {
) -> Result<OnlineClient<T>, OnlineClientError> {
let rpc_client = rpc_client.into();
let backend = Arc::new(LegacyBackend::builder().build(rpc_client));
OnlineClient::from_backend_with(genesis_hash, runtime_version, metadata, backend)
@@ -118,13 +121,23 @@ impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] by providing an underlying [`Backend`]
/// implementation to power it. Other details will be obtained from the chain.
pub async fn from_backend<B: Backend<T>>(backend: Arc<B>) -> Result<OnlineClient<T>, Error> {
let latest_block = backend.latest_finalized_block_ref().await?;
pub async fn from_backend<B: Backend<T>>(
backend: Arc<B>,
) -> Result<OnlineClient<T>, OnlineClientError> {
let latest_block = backend
.latest_finalized_block_ref()
.await
.map_err(OnlineClientError::CannotGetLatestFinalizedBlock)?;
let (genesis_hash, runtime_version, metadata) = future::join3(
backend.genesis_hash(),
backend.current_runtime_version(),
OnlineClient::fetch_metadata(&*backend, latest_block.hash()),
backend
.genesis_hash()
.map_err(OnlineClientError::CannotGetGenesisHash),
backend
.current_runtime_version()
.map_err(OnlineClientError::CannotGetCurrentRuntimeVersion),
OnlineClient::fetch_metadata(&*backend, latest_block.hash())
.map_err(OnlineClientError::CannotFetchMetadata),
)
.await;
@@ -148,7 +161,7 @@ impl<T: Config> OnlineClient<T> {
runtime_version: RuntimeVersion,
metadata: impl Into<Metadata>,
backend: Arc<B>,
) -> Result<OnlineClient<T>, Error> {
) -> Result<OnlineClient<T>, OnlineClientError> {
use subxt_core::config::Hasher;
let metadata = metadata.into();
@@ -169,7 +182,7 @@ impl<T: Config> OnlineClient<T> {
async fn fetch_metadata(
backend: &dyn Backend<T>,
block_hash: HashFor<T>,
) -> Result<Metadata, Error> {
) -> Result<Metadata, BackendError> {
#[cfg(feature = "unstable-metadata")]
{
/// The unstable metadata version number.
@@ -194,7 +207,7 @@ impl<T: Config> OnlineClient<T> {
async fn fetch_latest_stable_metadata(
backend: &dyn Backend<T>,
block_hash: HashFor<T>,
) -> Result<Metadata, Error> {
) -> Result<Metadata, BackendError> {
// The metadata versions we support in Subxt, from newest to oldest.
use subxt_metadata::SUPPORTED_METADATA_VERSIONS;
@@ -235,7 +248,7 @@ impl<T: Config> OnlineClient<T> {
/// tokio::spawn(async move {
/// let mut update_stream = updater.runtime_updates().await.unwrap();
///
/// while let Some(Ok(update)) = update_stream.next().await {
/// while let Ok(update) = update_stream.next().await {
/// let version = update.runtime_version().spec_version;
///
/// match updater.apply_update(update) {
@@ -416,9 +429,9 @@ impl<T: Config> ClientRuntimeUpdater<T> {
}
/// Tries to apply a new update.
pub fn apply_update(&self, update: Update) -> Result<(), UpgradeError> {
pub fn apply_update(&self, update: Update) -> Result<(), RuntimeUpdateeApplyError> {
if !self.is_runtime_version_different(&update.runtime_version) {
return Err(UpgradeError::SameVersion);
return Err(RuntimeUpdateeApplyError::SameVersion);
}
self.do_update(update);
@@ -430,12 +443,12 @@ impl<T: Config> ClientRuntimeUpdater<T> {
///
/// *Note:* This will run indefinitely until it errors, so the typical usage
/// would be to run it in a separate background task.
pub async fn perform_runtime_updates(&self) -> Result<(), Error> {
pub async fn perform_runtime_updates(&self) -> Result<(), RuntimeUpdaterError> {
// Obtain an update subscription to further detect changes in the runtime version of the node.
let mut runtime_version_stream = self.runtime_updates().await?;
while let Some(update) = runtime_version_stream.next().await {
let update = update?;
loop {
let update = runtime_version_stream.next().await?;
// This only fails if received the runtime version is the same the current runtime version
// which might occur because that runtime subscriptions in substrate sends out the initial
@@ -443,8 +456,6 @@ impl<T: Config> ClientRuntimeUpdater<T> {
// Thus, fine to ignore here as it strictly speaking isn't really an error
let _ = self.apply_update(update);
}
Ok(())
}
/// Low-level API to get runtime updates as a stream but it's doesn't check if the
@@ -452,9 +463,16 @@ impl<T: Config> ClientRuntimeUpdater<T> {
///
/// Instead that's up to the user of this API to decide when to update and
/// to perform the actual updating.
pub async fn runtime_updates(&self) -> Result<RuntimeUpdaterStream<T>, Error> {
pub async fn runtime_updates(&self) -> Result<RuntimeUpdaterStream<T>, RuntimeUpdaterError> {
let stream = self
.0
.backend()
.stream_runtime_version()
.await
.map_err(RuntimeUpdaterError::CannotStreamRuntimeVersion)?;
Ok(RuntimeUpdaterStream {
stream: self.0.backend().stream_runtime_version().await?,
stream,
client: self.0.clone(),
})
}
@@ -468,38 +486,27 @@ pub struct RuntimeUpdaterStream<T: Config> {
impl<T: Config> RuntimeUpdaterStream<T> {
/// Wait for the next runtime update.
pub async fn next(&mut self) -> Option<Result<Update, Error>> {
let runtime_version = match self.stream.next().await? {
Ok(runtime_version) => runtime_version,
Err(err) => return Some(Err(err)),
};
pub async fn next(&mut self) -> Result<Update, RuntimeUpdaterError> {
let runtime_version = self
.stream
.next()
.await
.ok_or(RuntimeUpdaterError::UnexpectedEndOfUpdateStream)?
.map_err(RuntimeUpdaterError::CannotGetNextRuntimeVersion)?;
let at =
match wait_runtime_upgrade_in_finalized_block(&self.client, &runtime_version).await? {
Ok(at) => at,
Err(err) => return Some(Err(err)),
};
let at = wait_runtime_upgrade_in_finalized_block(&self.client, &runtime_version).await?;
let metadata = match OnlineClient::fetch_metadata(self.client.backend(), at.hash()).await {
Ok(metadata) => metadata,
Err(err) => return Some(Err(err)),
};
let metadata = OnlineClient::fetch_metadata(self.client.backend(), at.hash())
.await
.map_err(RuntimeUpdaterError::CannotFetchNewMetadata)?;
Some(Ok(Update {
Ok(Update {
metadata,
runtime_version,
}))
})
}
}
/// Error that can occur during upgrade.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum UpgradeError {
/// The version is the same as the current version.
SameVersion,
}
/// Represents the state when a runtime upgrade occurred.
pub struct Update {
runtime_version: RuntimeVersion,
@@ -522,64 +529,52 @@ impl Update {
async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
client: &OnlineClient<T>,
runtime_version: &RuntimeVersion,
) -> Option<Result<BlockRef<HashFor<T>>, Error>> {
use scale_value::At;
) -> Result<BlockRef<HashFor<T>>, RuntimeUpdaterError> {
let hasher = client
.inner
.read()
.expect("Lock shouldn't be poisoned")
.hasher;
let mut block_sub = match client
let mut block_sub = client
.backend()
.stream_finalized_block_headers(hasher)
.await
{
Ok(s) => s,
Err(err) => return Some(Err(err)),
};
.map_err(RuntimeUpdaterError::CannotStreamFinalizedBlocks)?;
let block_ref = loop {
let (_, block_ref) = match block_sub.next().await? {
Ok(n) => n,
Err(err) => return Some(Err(err)),
};
let (_, block_ref) = block_sub
.next()
.await
.ok_or(RuntimeUpdaterError::UnexpectedEndOfBlockStream)?
.map_err(RuntimeUpdaterError::CannotGetNextFinalizedBlock)?;
let key: Vec<scale_value::Value> = vec![];
let addr = crate::dynamic::storage("System", "LastRuntimeUpgrade", key);
let addr =
crate::dynamic::storage::<(), scale_value::Value>("System", "LastRuntimeUpgrade");
let chunk = match client.storage().at(block_ref.hash()).fetch(&addr).await {
Ok(Some(v)) => v,
Ok(None) => {
// The storage `system::lastRuntimeUpgrade` should always exist.
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
unreachable!("The storage item `system::lastRuntimeUpgrade` should always exist")
}
Err(e) => return Some(Err(e)),
};
let client_at = client.storage().at(block_ref.hash());
let value = client_at
.entry(addr)
// The storage `system::lastRuntimeUpgrade` should always exist.
// <https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/system/src/lib.rs#L958>
.map_err(|_| RuntimeUpdaterError::CantFindSystemLastRuntimeUpgrade)?
.fetch(())
.await
.map_err(RuntimeUpdaterError::CantFetchLastRuntimeUpgrade)?
.decode_as::<LastRuntimeUpgrade>()
.map_err(RuntimeUpdaterError::CannotDecodeLastRuntimeUpgrade)?;
let scale_val = match chunk.to_value() {
Ok(v) => v,
Err(e) => return Some(Err(e.into())),
};
let Some(Ok(spec_version)) = scale_val
.at("spec_version")
.and_then(|v| v.as_u128())
.map(u32::try_from)
else {
return Some(Err(Error::Other(
"Decoding `RuntimeVersion::spec_version` as u32 failed".to_string(),
)));
};
#[derive(scale_decode::DecodeAsType)]
struct LastRuntimeUpgrade {
spec_version: u32,
}
// We are waiting for the chain to have the same spec version
// as sent out via the runtime subscription.
if spec_version == runtime_version.spec_version {
if value.spec_version == runtime_version.spec_version {
break block_ref;
}
};
Some(Ok(block_ref))
Ok(block_ref)
}
+11 -5
View File
@@ -2,7 +2,7 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{Config, client::OfflineClientT, error::Error};
use crate::{Config, client::OfflineClientT, error::ConstantError};
use derive_where::derive_where;
use subxt_core::constants::address::Address;
@@ -28,16 +28,22 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
/// the pallet or constant in question do not exist at all).
pub fn validate<Addr: Address>(&self, address: &Addr) -> Result<(), Error> {
pub fn validate<Addr: Address>(&self, address: Addr) -> Result<(), ConstantError> {
let metadata = self.client.metadata();
subxt_core::constants::validate(address, &metadata).map_err(Error::from)
subxt_core::constants::validate(address, &metadata)
}
/// Access the constant at the address given, returning the type defined by this address.
/// This is probably used with addresses given from static codegen, although you can manually
/// construct your own, too.
pub fn at<Addr: Address>(&self, address: &Addr) -> Result<Addr::Target, Error> {
pub fn at<Addr: Address>(&self, address: Addr) -> Result<Addr::Target, ConstantError> {
let metadata = self.client.metadata();
subxt_core::constants::get(address, &metadata).map_err(Error::from)
subxt_core::constants::get(address, &metadata)
}
/// Access the bytes of a constant by the address it is registered under.
pub fn bytes_at<Addr: Address>(&self, address: Addr) -> Result<Vec<u8>, ConstantError> {
let metadata = self.client.metadata();
subxt_core::constants::get_bytes(address, &metadata)
}
}

Some files were not shown because too many files have changed in this diff Show More