Move subxt-new to be the new subxt, and get first example working

This commit is contained in:
James Wilson
2025-12-11 17:44:18 +00:00
parent 15a7534a55
commit 02d0c12019
279 changed files with 1169 additions and 28576 deletions
Generated
+42 -128
View File
@@ -464,7 +464,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "artifacts"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"substrate-runner",
]
@@ -2132,7 +2132,7 @@ dependencies = [
[[package]]
name = "generate-custom-metadata"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"frame-metadata 23.0.0",
"parity-scale-codec",
@@ -2753,7 +2753,7 @@ dependencies = [
[[package]]
name = "integration-tests"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"assert_matches",
"cfg_aliases",
@@ -5599,7 +5599,7 @@ dependencies = [
[[package]]
name = "substrate-runner"
version = "0.44.0"
version = "0.50.0"
[[package]]
name = "subtle"
@@ -5609,25 +5609,31 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "subxt"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"assert_matches",
"async-trait",
"base58",
"bitvec",
"blake2",
"derive-where",
"either",
"frame-decode",
"frame-metadata 23.0.0",
"futures",
"hex",
"http-body",
"hyper",
"impl-serde",
"jsonrpsee",
"keccak-hash",
"parity-scale-codec",
"primitive-types",
"scale-bits",
"scale-decode",
"scale-encode",
"scale-info",
"scale-info-legacy",
"scale-value",
"serde",
"serde_json",
@@ -5635,12 +5641,12 @@ dependencies = [
"sp-crypto-hashing",
"sp-keyring",
"sp-runtime",
"subxt-core",
"subxt-lightclient",
"subxt-macro",
"subxt-metadata",
"subxt-rpcs",
"subxt-signer",
"subxt-utils-accountid32",
"thiserror 2.0.12",
"tokio",
"tokio-util",
@@ -5654,7 +5660,7 @@ dependencies = [
[[package]]
name = "subxt-cli"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"clap",
"color-eyre",
@@ -5688,7 +5694,7 @@ dependencies = [
[[package]]
name = "subxt-codegen"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"frame-metadata 23.0.0",
"getrandom 0.2.16",
@@ -5703,65 +5709,9 @@ dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "subxt-core"
version = "0.44.0"
dependencies = [
"assert_matches",
"base58",
"bitvec",
"blake2",
"derive-where",
"frame-decode",
"frame-metadata 23.0.0",
"hashbrown 0.14.5",
"hex",
"impl-serde",
"keccak-hash",
"parity-scale-codec",
"primitive-types",
"scale-bits",
"scale-decode",
"scale-encode",
"scale-info",
"scale-value",
"serde",
"serde_json",
"sp-core",
"sp-crypto-hashing",
"sp-keyring",
"subxt-macro",
"subxt-metadata",
"subxt-signer",
"thiserror 2.0.12",
"tracing",
]
[[package]]
name = "subxt-historic"
version = "0.0.8"
dependencies = [
"frame-decode",
"frame-metadata 23.0.0",
"futures",
"hex",
"parity-scale-codec",
"primitive-types",
"scale-decode",
"scale-info",
"scale-info-legacy",
"scale-type-resolver",
"scale-value",
"sp-crypto-hashing",
"subxt-rpcs",
"thiserror 2.0.12",
"tokio",
"url",
]
[[package]]
name = "subxt-lightclient"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"futures",
"futures-timer",
@@ -5786,7 +5736,7 @@ dependencies = [
[[package]]
name = "subxt-macro"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"darling",
"parity-scale-codec",
@@ -5806,7 +5756,7 @@ dependencies = [
[[package]]
name = "subxt-metadata"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"bitvec",
"criterion",
@@ -5823,59 +5773,9 @@ dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "subxt-new"
version = "0.44.0"
dependencies = [
"assert_matches",
"async-trait",
"base58",
"bitvec",
"blake2",
"derive-where",
"either",
"frame-decode",
"frame-metadata 23.0.0",
"futures",
"hex",
"http-body",
"hyper",
"impl-serde",
"jsonrpsee",
"keccak-hash",
"parity-scale-codec",
"primitive-types",
"scale-bits",
"scale-decode",
"scale-encode",
"scale-info",
"scale-info-legacy",
"scale-value",
"serde",
"serde_json",
"sp-core",
"sp-crypto-hashing",
"sp-keyring",
"sp-runtime",
"subxt-lightclient",
"subxt-macro",
"subxt-metadata",
"subxt-rpcs",
"subxt-signer",
"thiserror 2.0.12",
"tokio",
"tokio-util",
"tower",
"tracing",
"tracing-subscriber",
"url",
"wasm-bindgen-futures",
"web-time",
]
[[package]]
name = "subxt-rpcs"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"derive-where",
"finito",
@@ -5891,7 +5791,6 @@ dependencies = [
"primitive-types",
"serde",
"serde_json",
"subxt-core",
"subxt-lightclient",
"thiserror 2.0.12",
"tokio",
@@ -5904,7 +5803,7 @@ dependencies = [
[[package]]
name = "subxt-signer"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"base64 0.22.1",
"bip32",
@@ -5930,22 +5829,39 @@ dependencies = [
"sp-core",
"sp-crypto-hashing",
"sp-keyring",
"subxt-core",
"subxt",
"subxt-utils-accountid32",
"thiserror 2.0.12",
"zeroize",
]
[[package]]
name = "subxt-test-macro"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"quote",
"syn 2.0.101",
]
[[package]]
name = "subxt-utils-accountid32"
version = "0.50.0"
dependencies = [
"base58",
"blake2",
"parity-scale-codec",
"scale-decode",
"scale-encode",
"scale-info",
"serde",
"sp-core",
"sp-keyring",
"thiserror 2.0.12",
]
[[package]]
name = "subxt-utils-fetchmetadata"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"frame-metadata 23.0.0",
"hex",
@@ -5958,7 +5874,7 @@ dependencies = [
[[package]]
name = "subxt-utils-stripmetadata"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"either",
"frame-metadata 23.0.0",
@@ -6041,7 +5957,7 @@ dependencies = [
[[package]]
name = "test-runtime"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"hex",
"impl-serde",
@@ -6190,9 +6106,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
@@ -6469,7 +6383,7 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "ui-tests"
version = "0.44.0"
version = "0.50.0"
dependencies = [
"frame-metadata 23.0.0",
"generate-custom-metadata",
+13 -15
View File
@@ -1,11 +1,9 @@
[workspace]
members = [
"new",
"subxt",
"cli",
"codegen",
"core",
"lightclient",
"historic",
"testing/substrate-runner",
"testing/test-runtime",
"testing/integration-tests",
@@ -16,10 +14,10 @@ members = [
"metadata",
"rpcs",
"signer",
"subxt",
"scripts/artifacts",
"utils/fetch-metadata",
"utils/strip-metadata",
"utils/accountid32"
]
# We exclude any crates that would depend on non mutually
@@ -39,7 +37,7 @@ resolver = "2"
[workspace.package]
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2024"
version = "0.44.0"
version = "0.50.0"
rust-version = "1.85.0"
license = "Apache-2.0 OR GPL-3.0"
repository = "https://github.com/paritytech/subxt"
@@ -157,16 +155,16 @@ sp-state-machine = { version = "0.45.0", default-features = false }
sp-runtime = { version = "41.1.0", default-features = false }
# Subxt workspace crates:
subxt = { version = "0.44.0", path = "subxt", default-features = false }
subxt-core = { version = "0.44.0", path = "core", default-features = false }
subxt-macro = { version = "0.44.0", path = "macro" }
subxt-metadata = { version = "0.44.0", path = "metadata", default-features = false }
subxt-codegen = { version = "0.44.0", path = "codegen" }
subxt-signer = { version = "0.44.0", path = "signer", default-features = false }
subxt-rpcs = { version = "0.44.0", path = "rpcs", default-features = false }
subxt-lightclient = { version = "0.44.0", path = "lightclient", default-features = false }
subxt-utils-fetchmetadata = { version = "0.44.0", path = "utils/fetch-metadata", default-features = false }
subxt-utils-stripmetadata = { version = "0.44.0", path = "utils/strip-metadata", default-features = false }
subxt = { version = "0.50.0", path = "subxt", default-features = false }
subxt-macro = { version = "0.50.0", path = "macro" }
subxt-metadata = { version = "0.50.0", path = "metadata", default-features = false }
subxt-codegen = { version = "0.50.0", path = "codegen" }
subxt-signer = { version = "0.50.0", path = "signer", default-features = false }
subxt-rpcs = { version = "0.50.0", path = "rpcs", default-features = false }
subxt-lightclient = { version = "0.50.0", path = "lightclient", default-features = false }
subxt-utils-fetchmetadata = { version = "0.50.0", path = "utils/fetch-metadata", default-features = false }
subxt-utils-stripmetadata = { version = "0.50.0", path = "utils/strip-metadata", default-features = false }
subxt-utils-accountid32 = { version = "0.50.0", path = "utils/accountid32", default-features = false }
test-runtime = { path = "testing/test-runtime" }
substrate-runner = { path = "testing/substrate-runner" }
+6 -6
View File
@@ -17,7 +17,7 @@ use subxt_metadata::PalletMetadata;
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the calls are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::subxt::ext::subxt_core` when using subxt as a dependency.
/// - `crate_path` - The crate path under which the `subxt` crate is located, e.g. `::subxt` when using subxt as a dependency.
pub fn generate_calls(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
@@ -81,9 +81,9 @@ pub fn generate_calls(
#struct_def
#alias_mod
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
const PALLET: &'static str = #pallet_name;
const CALL: &'static str = #call_name;
impl #crate_path::extrinsics::DecodeAsExtrinsic for #struct_name {
const PALLET_NAME: &'static str = #pallet_name;
const CALL_NAME: &'static str = #call_name;
}
};
@@ -92,8 +92,8 @@ pub fn generate_calls(
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> #crate_path::tx::payload::StaticPayload<types::#struct_name> {
#crate_path::tx::payload::StaticPayload::new_static(
) -> #crate_path::transactions::payload::StaticPayload<types::#struct_name> {
#crate_path::transactions::payload::StaticPayload::new_static(
#pallet_name,
#call_name,
types::#struct_name { #( #call_args, )* },
+1 -1
View File
@@ -32,7 +32,7 @@ use super::CodegenError;
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the constants are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::subxt::ext::subxt_core` when using subxt as a dependency.
/// - `crate_path` - The crate path under which the `subxt` crate is located, e.g. `::subxt` when using subxt as a dependency.
pub fn generate_constants(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
+4 -4
View File
@@ -37,7 +37,7 @@ use subxt_metadata::PalletMetadata;
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the events are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::subxt::ext::subxt_core` when using subxt as a dependency.
/// - `crate_path` - The crate path under which the `subxt` crate is located, e.g. `::subxt` when using subxt as a dependency.
pub fn generate_events(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
@@ -63,9 +63,9 @@ pub fn generate_events(
#struct_def
#alias_mod
impl #crate_path::events::StaticEvent for #event_struct_name {
const PALLET: &'static str = #pallet_name;
const EVENT: &'static str = #event_name;
impl #crate_path::events::DecodeAsEvent for #event_struct_name {
const PALLET_NAME: &'static str = #pallet_name;
const EVENT_NAME: &'static str = #event_name;
}
}
});
+7 -2
View File
@@ -287,11 +287,16 @@ impl RuntimeGenerator {
StorageApi
}
/// This is an alias to [`Self::transactions()`].
pub fn tx() -> TransactionApi {
TransactionApi
}
pub fn apis() -> runtime_apis::RuntimeApi {
pub fn transactions() -> TransactionApi {
TransactionApi
}
pub fn runtime_apis() -> runtime_apis::RuntimeApi {
runtime_apis::RuntimeApi
}
@@ -301,7 +306,7 @@ impl RuntimeGenerator {
ViewFunctionsApi
}
pub fn custom() -> CustomValuesApi {
pub fn custom_values() -> CustomValuesApi {
CustomValuesApi
}
+2 -2
View File
@@ -183,11 +183,11 @@ fn generate_runtime_api(
pub fn #method_name(
&self,
#(#input_args),*
) -> #crate_path::runtime_api::payload::StaticPayload<
) -> #crate_path::runtime_apis::payload::StaticPayload<
(#(#input_tuple_types,)*),
#method_name::output::Output
> {
#crate_path::runtime_api::payload::StaticPayload::new_static(
#crate_path::runtime_apis::payload::StaticPayload::new_static(
#trait_name_str,
#method_name_str,
(#(#input_param_names,)*),
+1 -1
View File
@@ -19,7 +19,7 @@ use scale_typegen::typegen::ir::ToTokensWithSettings;
///
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the storage items are generated.
/// - `crate_path` - The crate path under which the `subxt-core` crate is located, e.g. `::subxt::ext::subxt_core` when using subxt as a dependency.
/// - `crate_path` - The crate path under which the `subxt` crate is located, e.g. `::subxt` when using subxt as a dependency.
pub fn generate_storage(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
+5 -6
View File
@@ -7,7 +7,6 @@
//! be used directly if preferable.
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod api;
pub mod error;
@@ -71,7 +70,7 @@ pub struct CodegenBuilder {
impl Default for CodegenBuilder {
fn default() -> Self {
CodegenBuilder {
crate_path: syn::parse_quote!(::subxt::ext::subxt_core),
crate_path: syn::parse_quote!(::subxt),
use_default_derives: true,
use_default_substitutions: true,
generate_docs: true,
@@ -216,7 +215,7 @@ impl CodegenBuilder {
self.item_mod = item_mod;
}
/// Set the path to the `subxt` crate. By default, we expect it to be at `::subxt::ext::subxt_core`.
/// Set the path to the `subxt` crate. By default, we expect it to be at `::subxt`.
///
/// # Panics
///
@@ -232,9 +231,9 @@ impl CodegenBuilder {
self.crate_path = crate_path;
}
/// Generate an interface, assuming that the default path to the `subxt` crate is `::subxt::ext::subxt_core`.
/// Generate an interface, assuming that the default path to the `subxt` crate is `::subxt`.
/// If the `subxt` crate is not available as a top level dependency, use `generate` and provide
/// a valid path to the `subxt¦ crate.
/// a valid path to the `subxt` crate.
pub fn generate(self, metadata: Metadata) -> Result<TokenStream2, CodegenError> {
let crate_path = self.crate_path;
@@ -300,7 +299,7 @@ impl CodegenBuilder {
/// The default [`scale_typegen::TypeGeneratorSettings`], subxt is using for generating code.
/// Useful for emulating subxt's code generation settings from e.g. subxt-explorer.
pub fn default_subxt_type_gen_settings() -> TypeGeneratorSettings {
let crate_path: syn::Path = parse_quote!(::subxt::ext::subxt_core);
let crate_path: syn::Path = parse_quote!(::subxt);
let derives = default_derives(&crate_path);
let substitutes = default_substitutes(&crate_path);
subxt_type_gen_settings(derives, substitutes, &crate_path, true)
-82
View File
@@ -1,82 +0,0 @@
[package]
name = "subxt-core"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
license.workspace = true
readme = "README.md"
repository.workspace = true
documentation.workspace = true
homepage.workspace = true
description = "A no-std compatible subset of Subxt's functionality"
keywords = ["parity", "subxt", "extrinsic", "no-std"]
[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"frame-metadata/std",
"subxt-metadata/std",
"hex/std",
"serde/std",
"serde_json/std",
"tracing/std",
"impl-serde/std",
"primitive-types/std",
"sp-core/std",
"sp-keyring/std",
"sp-crypto-hashing/std",
]
[dependencies]
codec = { package = "parity-scale-codec", workspace = true, default-features = false, features = ["derive"] }
frame-decode = { workspace = true }
scale-info = { workspace = true, default-features = false, features = ["bit-vec"] }
scale-value = { workspace = true, default-features = false }
scale-bits = { workspace = true, default-features = false }
scale-decode = { workspace = true, default-features = false, features = ["derive", "primitive-types"] }
scale-encode = { workspace = true, default-features = false, features = ["derive", "primitive-types", "bits"] }
frame-metadata = { workspace = true, default-features = false }
subxt-metadata = { workspace = true, default-features = false }
derive-where = { workspace = true }
hex = { workspace = true }
serde = { workspace = true, default-features = false, features = ["derive"] }
serde_json = { workspace = true, default-features = false, features = ["raw_value", "alloc"] }
tracing = { workspace = true, default-features = false }
sp-crypto-hashing = { workspace = true }
hashbrown = { workspace = true }
thiserror = { workspace = true, default-features = false }
# For ss58 encoding AccountId32 to serialize them properly:
base58 = { workspace = true }
blake2 = { workspace = true }
# Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256:
impl-serde = { workspace = true, default-features = false }
primitive-types = { workspace = true, default-features = false, features = ["codec", "serde_no_std", "scale-info"] }
# AccountId20
keccak-hash = { workspace = true}
[dev-dependencies]
assert_matches = { workspace = true }
bitvec = { workspace = true }
codec = { workspace = true, features = ["derive", "bit-vec"] }
subxt-macro = { workspace = true }
subxt-signer = { workspace = true, features = ["sr25519", "subxt"] }
sp-core = { workspace = true }
sp-keyring = { workspace = true }
hex = { workspace = true }
[package.metadata.docs.rs]
default-features = true
[package.metadata.playground]
default-features = true
[lints]
workspace = true
-3
View File
@@ -1,3 +0,0 @@
# Subxt-Core
This library provides a no-std compatible subset of functionality that `subxt` and `subxt-signer` rely on.
@@ -1,155 +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::config::TransactionExtension;
use crate::config::transaction_extensions::{
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
};
use crate::dynamic::Value;
use crate::error::ExtrinsicError;
use crate::{Metadata, config::Config};
use alloc::borrow::ToOwned;
use frame_decode::extrinsics::ExtrinsicExtensions;
use scale_decode::DecodeAsType;
/// The signed extensions of an extrinsic.
#[derive(Debug, Clone)]
pub struct ExtrinsicTransactionExtensions<'a, T: Config> {
bytes: &'a [u8],
metadata: &'a Metadata,
decoded_info: &'a ExtrinsicExtensions<'static, u32>,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Config> ExtrinsicTransactionExtensions<'a, T> {
pub(crate) fn new(
bytes: &'a [u8],
metadata: &'a Metadata,
decoded_info: &'a ExtrinsicExtensions<'static, u32>,
) -> Self {
Self {
bytes,
metadata,
decoded_info,
_marker: core::marker::PhantomData,
}
}
/// Returns an iterator over each of the signed extension details of the extrinsic.
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicTransactionExtension<'a, T>> + use<'a, T> {
self.decoded_info
.iter()
.map(|s| ExtrinsicTransactionExtension {
bytes: &self.bytes[s.range()],
ty_id: *s.ty(),
identifier: s.name(),
metadata: self.metadata,
_marker: core::marker::PhantomData,
})
}
/// 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>, ExtrinsicError> {
for ext in self.iter() {
match ext.as_signed_extension::<S>() {
// We found a match; return it:
Ok(Some(e)) => return Ok(Some(e)),
// No error, but no match either; next!
Ok(None) => continue,
// Error? return it
Err(e) => return Err(e),
}
}
Ok(None)
}
/// The tip of an extrinsic, extracted from the ChargeTransactionPayment or ChargeAssetTxPayment
/// signed extension, depending on which is present.
///
/// Returns `None` if `tip` was not found or decoding failed.
pub fn tip(&self) -> Option<u128> {
// Note: the overhead of iterating multiple time should be negligible.
self.find::<ChargeTransactionPayment>()
.ok()
.flatten()
.map(|e| e.tip())
.or_else(|| {
self.find::<ChargeAssetTxPayment<T>>()
.ok()
.flatten()
.map(|e| e.tip())
})
}
/// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension.
///
/// Returns `None` if `nonce` was not found or decoding failed.
pub fn nonce(&self) -> Option<u64> {
self.find::<CheckNonce>().ok()?
}
}
/// A single signed extension
#[derive(Debug, Clone)]
pub struct ExtrinsicTransactionExtension<'a, T: Config> {
bytes: &'a [u8],
ty_id: u32,
identifier: &'a str,
metadata: &'a Metadata,
_marker: core::marker::PhantomData<T>,
}
impl<'a, T: Config> ExtrinsicTransactionExtension<'a, T> {
/// The bytes representing this signed extension.
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
/// The name of the signed extension.
pub fn name(&self) -> &'a str {
self.identifier
}
/// The type id of the signed extension.
pub fn type_id(&self) -> u32 {
self.ty_id
}
/// Signed Extension as a [`scale_value::Value`]
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)
}
/// Decodes the bytes of this Signed Extension into its associated `Decoded` type.
/// Returns `Ok(None)` if the data we have doesn't match the Signed Extension we're asking to
/// decode with.
pub fn as_signed_extension<S: TransactionExtension<T>>(
&self,
) -> 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, 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)
}
}
-644
View File
@@ -1,644 +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::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
use crate::{
Metadata,
config::{Config, HashFor, Hasher},
error::{ExtrinsicDecodeErrorAt, ExtrinsicDecodeErrorAtReason, ExtrinsicError},
};
use alloc::sync::Arc;
use alloc::vec::Vec;
use frame_decode::extrinsics::Extrinsic;
use scale_decode::{DecodeAsFields, DecodeAsType};
pub use crate::blocks::StaticExtrinsic;
/// The body of a block.
pub struct Extrinsics<T: Config> {
extrinsics: Vec<Arc<(Extrinsic<'static, u32>, Vec<u8>)>>,
metadata: Metadata,
hasher: T::Hasher,
_marker: core::marker::PhantomData<T>,
}
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, ExtrinsicDecodeErrorAt> {
let hasher = T::Hasher::new(&metadata);
let extrinsics = extrinsics
.into_iter()
.enumerate()
.map(|(extrinsic_index, bytes)| {
let cursor = &mut &*bytes;
// Try to decode the extrinsic.
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(ExtrinsicDecodeErrorAt {
extrinsic_index,
error: ExtrinsicDecodeErrorAtReason::LeftoverBytes(cursor.to_vec()),
});
}
Ok(Arc::new((decoded_info, bytes)))
})
.collect::<Result<_, ExtrinsicDecodeErrorAt>>()?;
Ok(Self {
extrinsics,
hasher,
metadata,
_marker: core::marker::PhantomData,
})
}
/// The number of extrinsics.
pub fn len(&self) -> usize {
self.extrinsics.len()
}
/// Are there no extrinsics in this block?
// Note: mainly here to satisfy clippy.
pub fn is_empty(&self) -> bool {
self.extrinsics.is_empty()
}
/// Returns an iterator over the extrinsics in the block body.
// Dev note: The returned iterator is 'static + Send so that we can box it up and make
// use of it with our `FilterExtrinsic` stuff.
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicDetails<T>> + Send + Sync + 'static {
let extrinsics = self.extrinsics.clone();
let num_extrinsics = self.extrinsics.len();
let hasher = self.hasher;
let metadata = self.metadata.clone();
(0..num_extrinsics).map(move |index| {
ExtrinsicDetails::new(
index as u32,
extrinsics[index].clone(),
hasher,
metadata.clone(),
)
})
}
/// Iterate through the extrinsics using metadata to dynamically decode and skip
/// them, and return only those which should decode to the provided `E` type.
/// If an error occurs, all subsequent iterations return `None`.
pub fn find<E: StaticExtrinsic>(
&self,
) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, ExtrinsicError>> {
self.iter().filter_map(|details| {
match details.as_extrinsic::<E>() {
// Failed to decode extrinsic:
Err(err) => Some(Err(err)),
// Extrinsic for a different pallet / different call (skip):
Ok(None) => None,
Ok(Some(value)) => Some(Ok(FoundExtrinsic { details, value })),
}
})
}
/// 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>>, 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>>, 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, ExtrinsicError> {
Ok(self.find::<E>().next().transpose()?.is_some())
}
}
/// A single extrinsic in a block.
pub struct ExtrinsicDetails<T: Config> {
/// The index of the extrinsic in the block.
index: u32,
/// Extrinsic bytes and decode info.
ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
/// Hash the extrinsic if we want.
hasher: T::Hasher,
/// Subxt metadata to fetch the extrinsic metadata.
metadata: Metadata,
_marker: core::marker::PhantomData<T>,
}
impl<T> ExtrinsicDetails<T>
where
T: Config,
{
// Attempt to dynamically decode a single extrinsic from the given input.
#[doc(hidden)]
pub fn new(
index: u32,
ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
hasher: T::Hasher,
metadata: Metadata,
) -> ExtrinsicDetails<T> {
ExtrinsicDetails {
index,
ext,
hasher,
metadata,
_marker: core::marker::PhantomData,
}
}
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
pub fn hash(&self) -> HashFor<T> {
// Use hash(), not hash_of(), because we don't want to double encode the bytes.
self.hasher.hash(self.bytes())
}
/// Is the extrinsic signed?
pub fn is_signed(&self) -> bool {
self.decoded_info().is_signed()
}
/// The index of the extrinsic in the block.
pub fn index(&self) -> u32 {
self.index
}
/// Return _all_ of the bytes representing this extrinsic, which include, in order:
/// - First byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
/// - SignatureType (if the payload is signed)
/// - Address
/// - Signature
/// - Extra fields
/// - Extrinsic call bytes
pub fn bytes(&self) -> &[u8] {
&self.ext.1
}
/// Return only the bytes representing this extrinsic call:
/// - First byte is the pallet index
/// - Second byte is the variant (call) index
/// - Followed by field bytes.
///
/// # Note
///
/// Please use [`Self::bytes`] if you want to get all extrinsic bytes.
pub fn call_bytes(&self) -> &[u8] {
&self.bytes()[self.decoded_info().call_data_range()]
}
/// Return the bytes representing the fields stored in this extrinsic.
///
/// # Note
///
/// This is a subset of [`Self::call_bytes`] that does not include the
/// first two bytes that denote the pallet index and the variant index.
pub fn field_bytes(&self) -> &[u8] {
// Note: this cannot panic because we checked the extrinsic bytes
// to contain at least two bytes.
&self.bytes()[self.decoded_info().call_data_args_range()]
}
/// Return only the bytes of the address that signed this extrinsic.
///
/// # Note
///
/// Returns `None` if the extrinsic is not signed.
pub fn address_bytes(&self) -> Option<&[u8]> {
self.decoded_info()
.signature_payload()
.map(|s| &self.bytes()[s.address_range()])
}
/// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
pub fn signature_bytes(&self) -> Option<&[u8]> {
self.decoded_info()
.signature_payload()
.map(|s| &self.bytes()[s.signature_range()])
}
/// Returns the signed extension `extra` bytes of the extrinsic.
/// Each signed extension has an `extra` type (May be zero-sized).
/// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions.
/// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed.
///
/// Note: Returns `None` if the extrinsic is not signed.
pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> {
self.decoded_info()
.transaction_extension_payload()
.map(|t| &self.bytes()[t.range()])
}
/// Returns `None` if the extrinsic is not signed.
pub fn transaction_extensions(&self) -> Option<ExtrinsicTransactionExtensions<'_, T>> {
self.decoded_info()
.transaction_extension_payload()
.map(|t| ExtrinsicTransactionExtensions::new(self.bytes(), &self.metadata, t))
}
/// The index of the pallet that the extrinsic originated from.
pub fn pallet_index(&self) -> u8 {
self.decoded_info().pallet_index()
}
/// The index of the extrinsic variant that the extrinsic originated from.
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) -> &str {
self.decoded_info().pallet_name()
}
/// The name of the call (ie the name of the variant that it corresponds to).
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 decode_as_fields<E: DecodeAsFields>(&self) -> Result<E, ExtrinsicError> {
let bytes = &mut self.field_bytes();
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(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>, ExtrinsicError> {
if self.decoded_info().pallet_name() == E::PALLET
&& self.decoded_info().call_name() == E::CALL
{
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())
.map_err(|e| ExtrinsicError::CannotDecodeFields {
extrinsic_index: self.index as usize,
error: e,
})?;
Ok(Some(decoded))
} else {
Ok(None)
}
}
/// 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, 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)
}
fn decoded_info(&self) -> &Extrinsic<'static, u32> {
&self.ext.0
}
}
/// A Static Extrinsic found in a block coupled with it's details.
pub struct FoundExtrinsic<T: Config, E> {
/// Details for the extrinsic.
pub details: ExtrinsicDetails<T>,
/// The decoded extrinsic value.
pub value: E,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::SubstrateConfig;
use assert_matches::assert_matches;
use codec::{Decode, Encode};
use frame_metadata::v15::{CustomMetadata, OuterEnums};
use frame_metadata::{
RuntimeMetadataPrefixed,
v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
};
use scale_info::{TypeInfo, meta_type};
use scale_value::Value;
// Extrinsic needs to contain at least the generic type parameter "Call"
// for the metadata to be valid.
// The "Call" type from the metadata is used to decode extrinsics.
#[allow(unused)]
#[derive(TypeInfo)]
struct ExtrinsicType<Address, Call, Signature, Extra> {
pub signature: Option<(Address, Signature, Extra)>,
pub function: Call,
}
// Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
// Each pallet must contain one single variant.
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
enum RuntimeCall {
Test(Pallet),
}
// The calls of the pallet.
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
enum Pallet {
#[allow(unused)]
#[codec(index = 2)]
TestCall {
value: u128,
signed: bool,
name: String,
},
}
#[allow(unused)]
#[derive(
Encode,
Decode,
TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
)]
struct TestCallExtrinsic {
value: u128,
signed: bool,
name: String,
}
impl StaticExtrinsic for TestCallExtrinsic {
const PALLET: &'static str = "Test";
const CALL: &'static str = "TestCall";
}
/// Build fake metadata consisting the types needed to represent an extrinsic.
fn metadata() -> Metadata {
let pallets = vec![PalletMetadata {
name: "Test",
storage: None,
calls: Some(PalletCallMetadata {
ty: meta_type::<Pallet>(),
}),
event: None,
constants: vec![],
error: None,
index: 0,
docs: vec![],
}];
let extrinsic = ExtrinsicMetadata {
version: 4,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<RuntimeCall>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let meta = RuntimeMetadataV15::new(
pallets,
extrinsic,
meta_type::<()>(),
vec![],
OuterEnums {
call_enum_ty: meta_type::<RuntimeCall>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
CustomMetadata {
map: Default::default(),
},
);
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
metadata
}
#[test]
fn extrinsic_metadata_consistency() {
let metadata = metadata();
// Except our metadata to contain the registered types.
let pallet = metadata.pallet_by_call_index(0).expect("pallet exists");
let extrinsic = pallet
.call_variant_by_index(2)
.expect("metadata contains the RuntimeCall enum with this pallet");
assert_eq!(pallet.name(), "Test");
assert_eq!(&extrinsic.name, "TestCall");
}
#[test]
fn insufficient_extrinsic_bytes() {
let metadata = metadata();
// Decode with empty bytes.
let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![]], metadata);
assert_matches!(
result.err(),
Some(crate::error::ExtrinsicDecodeErrorAt {
extrinsic_index: 0,
error: _
})
);
}
#[test]
fn unsupported_version_extrinsic() {
use frame_decode::extrinsics::ExtrinsicDecodeError;
let metadata = metadata();
// Decode with invalid version.
let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![3u8].encode()], metadata);
assert_matches!(
result.err(),
Some(crate::error::ExtrinsicDecodeErrorAt {
extrinsic_index: 0,
error: ExtrinsicDecodeErrorAtReason::DecodeError(
ExtrinsicDecodeError::VersionNotSupported(3)
),
})
);
}
#[test]
fn tx_hashes_line_up() {
let metadata = metadata();
let hasher = <SubstrateConfig as Config>::Hasher::new(&metadata);
let tx = crate::dynamic::tx(
"Test",
"TestCall",
vec![
Value::u128(10),
Value::bool(true),
Value::string("SomeValue"),
],
);
// Encoded TX ready to submit.
let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
.expect("Valid dynamic parameters are provided");
// Extrinsic details ready to decode.
let extrinsics = Extrinsics::<SubstrateConfig>::decode_from(
vec![tx_encoded.encoded().to_owned()],
metadata,
)
.expect("Valid extrinsic");
let extrinsic = extrinsics.iter().next().unwrap();
// Both of these types should produce the same bytes.
assert_eq!(tx_encoded.encoded(), extrinsic.bytes(), "bytes should eq");
// Both of these types should produce the same hash.
assert_eq!(
tx_encoded.hash_with(hasher),
extrinsic.hash(),
"hashes should eq"
);
}
#[test]
fn statically_decode_extrinsic() {
let metadata = metadata();
let tx = crate::dynamic::tx(
"Test",
"TestCall",
vec![
Value::u128(10),
Value::bool(true),
Value::string("SomeValue"),
],
);
let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
.expect("Valid dynamic parameters are provided");
// Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
// The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed.
let extrinsics = Extrinsics::<SubstrateConfig>::decode_from(
vec![tx_encoded.encoded().to_owned()],
metadata,
)
.expect("Valid extrinsic");
let extrinsic = extrinsics.iter().next().unwrap();
assert!(!extrinsic.is_signed());
assert_eq!(extrinsic.index(), 0);
assert_eq!(extrinsic.pallet_index(), 0);
assert_eq!(extrinsic.pallet_name(), "Test");
assert_eq!(extrinsic.call_index(), 2);
assert_eq!(extrinsic.call_name(), "TestCall");
// Decode the extrinsic to the root enum.
let decoded_extrinsic = extrinsic
.as_root_extrinsic::<RuntimeCall>()
.expect("can decode extrinsic to root enum");
assert_eq!(
decoded_extrinsic,
RuntimeCall::Test(Pallet::TestCall {
value: 10,
signed: true,
name: "SomeValue".into(),
})
);
// Decode the extrinsic to the extrinsic variant.
let decoded_extrinsic = extrinsic
.as_extrinsic::<TestCallExtrinsic>()
.expect("can decode extrinsic to extrinsic variant")
.expect("value cannot be None");
assert_eq!(
decoded_extrinsic,
TestCallExtrinsic {
value: 10,
signed: true,
name: "SomeValue".into(),
}
);
}
}
-91
View File
@@ -1,91 +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.
//! Decode and iterate over the extrinsics in block bodies.
//!
//! Use the [`decode_from`] function as an entry point to decoding extrinsics, and then
//! have a look at [`Extrinsics`] and [`ExtrinsicDetails`] to see which methods are available
//! to work with the extrinsics.
//!
//! # Example
//!
//! ```rust
//! extern crate alloc;
//!
//! use subxt_macro::subxt;
//! use subxt_core::blocks;
//! use subxt_core::Metadata;
//! use subxt_core::config::PolkadotConfig;
//! use alloc::vec;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // 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();
//!
//! // Some extrinsics we'd like to decode:
//! let ext_bytes = vec![
//! hex::decode("1004020000").unwrap(),
//! hex::decode("c10184001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c01a27c400241aeafdea1871b32f1f01e92acd272ddfe6b2f8b73b64c606572a530c470a94ef654f7baa5828474754a1fe31b59f91f6bb5c2cd5a07c22d4b8b8387350100000000001448656c6c6f").unwrap(),
//! hex::decode("550284001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c0144bb92734447c893ab16d520fae0d455257550efa28ee66bf6dc942cb8b00d5d2799b98bc2865d21812278a9a266acd7352f40742ff11a6ce1f400013961598485010000000400008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a481700505a4f7e9f4eb106").unwrap()
//! ];
//!
//! // Given some chain config and metadata, we know how to decode the bytes.
//! let exts = blocks::decode_from::<PolkadotConfig>(ext_bytes, metadata).unwrap();
//!
//! // We'll see 3 extrinsics:
//! assert_eq!(exts.len(), 3);
//!
//! // We can iterate over them and decode various details out of them.
//! for ext in exts.iter() {
//! println!("Pallet: {}", ext.pallet_name());
//! println!("Call: {}", ext.call_name());
//! }
//!
//! # let ext_details: Vec<_> = exts.iter()
//! # .map(|ext| {
//! # let pallet = ext.pallet_name().to_string();
//! # let call = ext.call_name().to_string();
//! # (pallet, call)
//! # })
//! # .collect();
//! #
//! # assert_eq!(ext_details, vec![
//! # ("Timestamp".to_owned(), "set".to_owned()),
//! # ("System".to_owned(), "remark".to_owned()),
//! # ("Balances".to_owned(), "transfer_allow_death".to_owned()),
//! # ]);
//! ```
mod extrinsic_transaction_extensions;
mod extrinsics;
mod static_extrinsic;
use crate::Metadata;
use crate::config::Config;
use crate::error::ExtrinsicDecodeErrorAt;
pub use crate::error::ExtrinsicError;
use alloc::vec::Vec;
pub use extrinsic_transaction_extensions::{
ExtrinsicTransactionExtension, ExtrinsicTransactionExtensions,
};
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
/// form of bytes) and some metadata that we'll use to decode them.
///
/// This is a shortcut for [`Extrinsics::decode_from`].
pub fn decode_from<T: Config>(
extrinsics: Vec<Vec<u8>>,
metadata: Metadata,
) -> Result<Extrinsics<T>, ExtrinsicDecodeErrorAt> {
Extrinsics::decode_from(extrinsics, metadata)
}
-23
View File
@@ -1,23 +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 scale_decode::DecodeAsFields;
/// Trait to uniquely identify the extrinsic's identity from the runtime metadata.
///
/// Generated API structures that represent an extrinsic implement this trait.
///
/// The trait is utilized to decode emitted extrinsics from a block, via obtaining the
/// form of the `Extrinsic` from the metadata.
pub trait StaticExtrinsic: DecodeAsFields {
/// Pallet name.
const PALLET: &'static str;
/// Call name.
const CALL: &'static str;
/// Returns true if the given pallet and call names match this extrinsic.
fn is_extrinsic(pallet: &str, call: &str) -> bool {
Self::PALLET == pallet && Self::CALL == call
}
}
-42
View File
@@ -1,42 +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 couple of client types that we use elsewhere.
use crate::{
Metadata,
config::{Config, HashFor},
};
use derive_where::derive_where;
/// This provides access to some relevant client state in transaction extensions,
/// and is just a combination of some of the available properties.
#[derive_where(Clone, Debug)]
pub struct ClientState<C: Config> {
/// Genesis hash.
pub genesis_hash: HashFor<C>,
/// Runtime version.
pub runtime_version: RuntimeVersion,
/// Metadata.
pub metadata: Metadata,
}
/// Runtime version information needed to submit transactions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeVersion {
/// Version of the runtime specification. A full-node will not attempt to use its native
/// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`,
/// `spec_version` and `authoring_version` are the same between Wasm and native.
pub spec_version: u32,
/// All existing dispatches are fully compatible when this number doesn't change. If this
/// number changes, then `spec_version` must change, also.
///
/// This number must change when an existing dispatchable (module ID, dispatch ID) is changed,
/// either through an alteration in its user-level semantics, a parameter
/// added/removed/changed, a dispatchable being removed, a module being removed, or a
/// dispatchable/module changing its index.
///
/// It need *not* change when a new module is added or when a dispatchable is added.
pub transaction_version: u32,
}
-168
View File
@@ -1,168 +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::config::transaction_extensions::CheckMortalityParams;
use super::{Config, HashFor};
use super::{ExtrinsicParams, transaction_extensions};
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
/// and how to apply them to a given chain.
pub type DefaultExtrinsicParams<T> = transaction_extensions::AnyOf<
T,
(
transaction_extensions::VerifySignature<T>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<T>,
transaction_extensions::CheckMortality<T>,
transaction_extensions::ChargeAssetTxPayment<T>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
),
>;
/// A builder that outputs the set of [`super::ExtrinsicParams::Params`] required for
/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current
/// chain; such values will simply be ignored if so.
pub struct DefaultExtrinsicParamsBuilder<T: Config> {
/// `None` means the tx will be immortal, else it's mortality is described.
mortality: transaction_extensions::CheckMortalityParams<T>,
/// `None` means the nonce will be automatically set.
nonce: Option<u64>,
/// `None` means we'll use the native token.
tip_of_asset_id: Option<T::AssetId>,
tip: u128,
tip_of: u128,
}
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
fn default() -> Self {
Self {
mortality: CheckMortalityParams::default(),
tip: 0,
tip_of: 0,
tip_of_asset_id: None,
nonce: None,
}
}
}
impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
/// Configure new extrinsic params. We default to providing no tip
/// and using an immortal transaction unless otherwise configured
pub fn new() -> Self {
Default::default()
}
/// Make the transaction immortal, meaning it will never expire. This means that it could, in
/// theory, be pending for a long time and only be included many blocks into the future.
pub fn immortal(mut self) -> Self {
self.mortality = transaction_extensions::CheckMortalityParams::immortal();
self
}
/// Make the transaction mortal, given a number of blocks it will be mortal for from
/// the current block at the time of submission.
///
/// # Warning
///
/// This will ultimately return an error if used for creating extrinsic offline, because we need
/// additional information in order to set the mortality properly.
///
/// When creating offline transactions, you must use [`Self::mortal_from_unchecked`] instead to set
/// the mortality. This provides all of the necessary information which we must otherwise be online
/// in order to obtain.
pub fn mortal(mut self, for_n_blocks: u64) -> Self {
self.mortality = transaction_extensions::CheckMortalityParams::mortal(for_n_blocks);
self
}
/// Configure a transaction that will be mortal for the number of blocks given, and from the
/// block details provided. Prefer to use [`Self::mortal()`] where possible, which prevents
/// the block number and hash from being misaligned.
pub fn mortal_from_unchecked(
mut self,
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: HashFor<T>,
) -> Self {
self.mortality = transaction_extensions::CheckMortalityParams::mortal_from_unchecked(
for_n_blocks,
from_block_n,
from_block_hash,
);
self
}
/// Provide a specific nonce for the submitter of the extrinsic
pub fn nonce(mut self, nonce: u64) -> Self {
self.nonce = Some(nonce);
self
}
/// Provide a tip to the block author in the chain's native token.
pub fn tip(mut self, tip: u128) -> Self {
self.tip = tip;
self.tip_of = tip;
self.tip_of_asset_id = None;
self
}
/// Provide a tip to the block author using the token denominated by the `asset_id` provided. This
/// is not applicable on chains which don't use the `ChargeAssetTxPayment` signed extension; in this
/// case, no tip will be given.
pub fn tip_of(mut self, tip: u128, asset_id: T::AssetId) -> Self {
self.tip = 0;
self.tip_of = tip;
self.tip_of_asset_id = Some(asset_id);
self
}
/// Build the extrinsic parameters.
pub fn build(self) -> <DefaultExtrinsicParams<T> as ExtrinsicParams<T>>::Params {
let check_mortality_params = self.mortality;
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
transaction_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
} else {
transaction_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
};
let charge_transaction_params =
transaction_extensions::ChargeTransactionPaymentParams::tip(self.tip);
let check_nonce_params = if let Some(nonce) = self.nonce {
transaction_extensions::CheckNonceParams::with_nonce(nonce)
} else {
transaction_extensions::CheckNonceParams::from_chain()
};
(
(),
(),
(),
check_nonce_params,
(),
check_mortality_params,
charge_asset_tx_params,
charge_transaction_params,
(),
)
}
}
#[cfg(test)]
mod test {
use super::*;
fn assert_default<T: Default>(_t: T) {}
#[test]
fn params_are_default() {
let params = DefaultExtrinsicParamsBuilder::<crate::config::PolkadotConfig>::new().build();
assert_default(params)
}
}
-128
View File
@@ -1,128 +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.
//! This module contains a trait which controls the parameters that must
//! be provided in order to successfully construct an extrinsic.
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
//! implementation of this that will work in many cases.
use crate::{
client::ClientState,
config::{Config, HashFor},
error::ExtrinsicParamsError,
};
use alloc::vec::Vec;
use core::any::Any;
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are a part of the transaction payload
/// or the signer payload respectively.
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + Send + 'static {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
type Params: Params<T>;
/// Construct a new instance of our [`ExtrinsicParams`].
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError>;
}
/// This trait is expected to be implemented for any [`ExtrinsicParams`], and
/// defines how to encode the "additional" and "extra" params. Both functions
/// are optional and will encode nothing by default.
pub trait ExtrinsicParamsEncoder: 'static {
/// This is expected to SCALE encode the transaction extension data to some
/// buffer that has been provided. This data is attached to the transaction
/// and also (by default) attached to the signer payload which is signed to
/// provide a signature for the transaction.
///
/// If [`ExtrinsicParamsEncoder::encode_signer_payload_value_to`] is implemented,
/// then that will be used instead when generating a signer payload. Useful for
/// eg the `VerifySignature` extension, which is send with the transaction but
/// is not a part of the signer payload.
fn encode_value_to(&self, _v: &mut Vec<u8>) {}
/// See [`ExtrinsicParamsEncoder::encode_value_to`]. This defaults to calling that
/// method, but if implemented will dictate what is encoded to the signer payload.
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
self.encode_value_to(v);
}
/// This is expected to SCALE encode the "implicit" (formally "additional")
/// parameters to some buffer that has been provided. These parameters are
/// _not_ sent along with the transaction, but are taken into account when
/// signing it, meaning the client and node must agree on their values.
fn encode_implicit_to(&self, _v: &mut Vec<u8>) {}
/// Set the signature. This happens after we have constructed the extrinsic params,
/// and so is defined here rather than on the params, below. We need to use `&dyn Any`
/// to keep this trait object safe, but can downcast in the impls.
///
/// # Panics
///
/// Implementations of this will likely try to downcast the provided `account_id`
/// and `signature` into `T::AccountId` and `T::Signature` (where `T: Config`), and are
/// free to panic if this downcasting does not succeed.
///
/// In typical usage, this is not a problem, since this method is only called internally
/// and provided values which line up with the relevant `Config`. In theory though, this
/// method can be called manually with any types, hence this warning.
fn inject_signature(&mut self, _account_id: &dyn Any, _signature: &dyn Any) {}
}
/// The parameters (ie [`ExtrinsicParams::Params`]) can also have data injected into them,
/// allowing Subxt to retrieve data from the chain and amend the parameters with it when
/// online.
pub trait Params<T: Config> {
/// Set the account nonce.
fn inject_account_nonce(&mut self, _nonce: u64) {}
/// Set the current block.
fn inject_block(&mut self, _number: u64, _hash: HashFor<T>) {}
}
impl<T: Config> Params<T> for () {}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
impl <Conf: Config, $($ident : Params<Conf>),+> Params<Conf> for ($($ident,)+){
fn inject_account_nonce(&mut self, nonce: u64) {
$(self.$index.inject_account_nonce(nonce);)+
}
fn inject_block(&mut self, number: u64, hash: HashFor<Conf>) {
$(self.$index.inject_block(number, hash);)+
}
}
}
}
#[rustfmt::skip]
const _: () = {
impl_tuples!(A 0);
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);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19, U 20);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19, U 20, V 21);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19, U 20, V 21, W 22);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19, U 20, V 21, W 22, X 23);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19, U 20, V 21, W 22, X 23, Y 24);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, T 19, U 20, V 21, W 22, X 23, Y 24, Z 25);
};
-130
View File
@@ -1,130 +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.
//! This module provides a [`Config`] type, which is used to define various
//! types that are important in order to speak to a particular chain.
//! [`SubstrateConfig`] provides a default set of these types suitable for the
//! default Substrate node implementation, and [`PolkadotConfig`] for a
//! Polkadot node.
mod default_extrinsic_params;
mod extrinsic_params;
pub mod polkadot;
pub mod substrate;
pub mod transaction_extensions;
use codec::{Decode, Encode};
use core::fmt::Debug;
use scale_decode::DecodeAsType;
use scale_encode::EncodeAsType;
use serde::{Serialize, de::DeserializeOwned};
use subxt_metadata::Metadata;
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
pub use transaction_extensions::TransactionExtension;
/// Runtime types.
// Note: the `Send + Sync + 'static` bound isn't strictly required, but currently deriving
// TypeInfo automatically applies a 'static bound to all generic types (including this one),
// And we want the compiler to infer `Send` and `Sync` OK for things which have `T: Config`
// rather than having to `unsafe impl` them ourselves.
pub trait Config: Sized + Send + Sync + 'static {
/// The account ID type.
type AccountId: Debug + Clone + Encode + Decode + Serialize + Send;
/// The address type.
type Address: Debug + Encode + From<Self::AccountId>;
/// The signature type.
type Signature: Debug + Clone + Encode + Decode + Send;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
type Hasher: Debug + Clone + Copy + Hasher + Send + Sync;
/// The block header.
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned + Clone;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: ExtrinsicParams<Self>;
/// This is used to identify an asset in the `ChargeAssetTxPayment` signed extension.
type AssetId: Debug + Clone + Encode + DecodeAsType + EncodeAsType + Send;
}
/// Given some [`Config`], this returns the type of hash used.
pub type HashFor<T> = <<T as Config>::Hasher as Hasher>::Output;
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
pub type ParamsFor<T> = <<T as Config>::ExtrinsicParams as ExtrinsicParams<T>>::Params;
/// Block hashes must conform to a bunch of things to be used in Subxt.
pub trait Hash:
Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ core::hash::Hash
{
}
impl<T> Hash for T where
T: Debug
+ Copy
+ Send
+ Sync
+ Decode
+ AsRef<[u8]>
+ Serialize
+ DeserializeOwned
+ Encode
+ PartialEq
+ Eq
+ core::hash::Hash
{
}
/// This represents the hasher used by a node to hash things like block headers
/// and extrinsics.
pub trait Hasher {
/// The type given back from the hash operation
type Output: Hash;
/// Construct a new hasher.
fn new(metadata: &Metadata) -> Self;
/// Hash some bytes to the given output type.
fn hash(&self, s: &[u8]) -> Self::Output;
/// Hash some SCALE encodable type to the given output type.
fn hash_of<S: Encode>(&self, s: &S) -> Self::Output {
let out = s.encode();
self.hash(&out)
}
}
/// This represents the block header type used by a node.
pub trait Header: Sized + Encode + Decode {
/// The block number type for this header.
type Number: Into<u64>;
/// The hasher used to hash this header.
type Hasher: Hasher;
/// Return the block number of this header.
fn number(&self) -> Self::Number;
/// Hash this header.
fn hash_with(&self, hasher: Self::Hasher) -> <Self::Hasher as Hasher>::Output {
hasher.hash_of(self)
}
}
-41
View File
@@ -1,41 +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.
//! Polkadot specific configuration
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
use crate::config::SubstrateConfig;
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
pub use primitive_types::{H256, U256};
/// Default set of commonly used types by Polkadot nodes.
// Note: The trait implementations exist just to make life easier,
// but shouldn't strictly be necessary since users can't instantiate this type.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum PolkadotConfig {}
impl Config for PolkadotConfig {
type AccountId = <SubstrateConfig as Config>::AccountId;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type AssetId = <SubstrateConfig as Config>::AssetId;
// Address on Polkadot has no account index, whereas it's u32 on
// the default substrate dev node.
type Address = MultiAddress<Self::AccountId, ()>;
// These are the same as the default substrate node, but redefined
// because we need to pass the PolkadotConfig trait as a param.
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for a polkadot node.
pub type PolkadotExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type PolkadotExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
-396
View File
@@ -1,396 +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.
//! Substrate specific configuration
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, Hasher, Header};
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
use alloc::format;
use alloc::vec::Vec;
use codec::{Decode, Encode};
pub use primitive_types::{H256, U256};
use serde::{Deserialize, Serialize};
use subxt_metadata::Metadata;
/// Default set of commonly used types by Substrate runtimes.
// Note: We only use this at the type level, so it should be impossible to
// create an instance of it.
// The trait implementations exist just to make life easier,
// but shouldn't strictly be necessary since users can't instantiate this type.
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum SubstrateConfig {}
impl Config for SubstrateConfig {
type AccountId = AccountId32;
type Address = MultiAddress<Self::AccountId, u32>;
type Signature = MultiSignature;
type Hasher = DynamicHasher256;
type Header = SubstrateHeader<u32, DynamicHasher256>;
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
type AssetId = u32;
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for the default substrate node.
pub type SubstrateExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type SubstrateExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
/// A hasher (ie implements [`Hasher`]) which hashes values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlakeTwo256;
impl Hasher for BlakeTwo256 {
type Output = H256;
fn new(_metadata: &Metadata) -> Self {
Self
}
fn hash(&self, s: &[u8]) -> Self::Output {
sp_crypto_hashing::blake2_256(s).into()
}
}
/// A hasher (ie implements [`Hasher`]) which inspects the runtime metadata to decide how to
/// hash types, falling back to blake2_256 if the hasher information is not available.
///
/// Currently this hasher supports only `BlakeTwo256` and `Keccak256` hashing methods.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DynamicHasher256(HashType);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HashType {
// Most chains use this:
BlakeTwo256,
// Chains like Hyperbridge use this (tends to be eth compatible chains)
Keccak256,
// If we don't have V16 metadata, we'll emit this and default to BlakeTwo256.
Unknown,
}
impl Hasher for DynamicHasher256 {
type Output = H256;
fn new(metadata: &Metadata) -> Self {
// Determine the Hash associated type used for the current chain, if possible.
let Some(system_pallet) = metadata.pallet_by_name("System") else {
return Self(HashType::Unknown);
};
let Some(hash_ty_id) = system_pallet.associated_type_id("Hashing") else {
return Self(HashType::Unknown);
};
let ty = metadata
.types()
.resolve(hash_ty_id)
.expect("Type information for 'Hashing' associated type should be in metadata");
let hash_type = match ty.path.ident().as_deref().unwrap_or("") {
"BlakeTwo256" => HashType::BlakeTwo256,
"Keccak256" => HashType::Keccak256,
_ => HashType::Unknown,
};
Self(hash_type)
}
fn hash(&self, s: &[u8]) -> Self::Output {
match self.0 {
HashType::BlakeTwo256 | HashType::Unknown => sp_crypto_hashing::blake2_256(s).into(),
HashType::Keccak256 => sp_crypto_hashing::keccak_256(s).into(),
}
}
}
/// A generic Substrate header type, adapted from `sp_runtime::generic::Header`.
/// The block number and hasher can be configured to adapt this for other nodes.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubstrateHeader<N: Copy + Into<U256> + TryFrom<U256>, H: Hasher> {
/// The parent hash.
pub parent_hash: H::Output,
/// The block number.
#[serde(
serialize_with = "serialize_number",
deserialize_with = "deserialize_number"
)]
#[codec(compact)]
pub number: N,
/// The state trie merkle root
pub state_root: H::Output,
/// The merkle root of the extrinsics.
pub extrinsics_root: H::Output,
/// A chain-specific digest of data useful for light clients or referencing auxiliary data.
pub digest: Digest,
}
impl<N, H> Header for SubstrateHeader<N, H>
where
N: Copy + Into<u64> + Into<U256> + TryFrom<U256> + Encode,
H: Hasher,
SubstrateHeader<N, H>: Encode + Decode,
{
type Number = N;
type Hasher = H;
fn number(&self) -> Self::Number {
self.number
}
}
/// Generic header digest. From `sp_runtime::generic::digest`.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
pub struct Digest {
/// A list of digest items.
pub logs: Vec<DigestItem>,
}
/// Digest item that is able to encode/decode 'system' digest items and
/// provide opaque access to other items. From `sp_runtime::generic::digest`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DigestItem {
/// A pre-runtime digest.
///
/// These are messages from the consensus engine to the runtime, although
/// the consensus engine can (and should) read them itself to avoid
/// code and state duplication. It is erroneous for a runtime to produce
/// these, but this is not (yet) checked.
///
/// NOTE: the runtime is not allowed to panic or fail in an `on_initialize`
/// call if an expected `PreRuntime` digest is not present. It is the
/// responsibility of a external block verifier to check this. Runtime API calls
/// will initialize the block without pre-runtime digests, so initialization
/// cannot fail when they are missing.
PreRuntime(ConsensusEngineId, Vec<u8>),
/// A message from the runtime to the consensus engine. This should *never*
/// be generated by the native code of any consensus engine, but this is not
/// checked (yet).
Consensus(ConsensusEngineId, Vec<u8>),
/// Put a Seal on it. This is only used by native code, and is never seen
/// by runtimes.
Seal(ConsensusEngineId, Vec<u8>),
/// Some other thing. Unsupported and experimental.
Other(Vec<u8>),
/// An indication for the light clients that the runtime execution
/// environment is updated.
///
/// Currently this is triggered when:
/// 1. Runtime code blob is changed or
/// 2. `heap_pages` value is changed.
RuntimeEnvironmentUpdated,
}
// From sp_runtime::generic, DigestItem enum indexes are encoded using this:
#[repr(u32)]
#[derive(Encode, Decode)]
enum DigestItemType {
Other = 0u32,
Consensus = 4u32,
Seal = 5u32,
PreRuntime = 6u32,
RuntimeEnvironmentUpdated = 8u32,
}
impl Encode for DigestItem {
fn encode(&self) -> Vec<u8> {
let mut v = Vec::new();
match self {
Self::Consensus(val, data) => {
DigestItemType::Consensus.encode_to(&mut v);
(val, data).encode_to(&mut v);
}
Self::Seal(val, sig) => {
DigestItemType::Seal.encode_to(&mut v);
(val, sig).encode_to(&mut v);
}
Self::PreRuntime(val, data) => {
DigestItemType::PreRuntime.encode_to(&mut v);
(val, data).encode_to(&mut v);
}
Self::Other(val) => {
DigestItemType::Other.encode_to(&mut v);
val.encode_to(&mut v);
}
Self::RuntimeEnvironmentUpdated => {
DigestItemType::RuntimeEnvironmentUpdated.encode_to(&mut v);
}
}
v
}
}
impl Decode for DigestItem {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let item_type: DigestItemType = Decode::decode(input)?;
match item_type {
DigestItemType::PreRuntime => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::PreRuntime(vals.0, vals.1))
}
DigestItemType::Consensus => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::Consensus(vals.0, vals.1))
}
DigestItemType::Seal => {
let vals: (ConsensusEngineId, Vec<u8>) = Decode::decode(input)?;
Ok(Self::Seal(vals.0, vals.1))
}
DigestItemType::Other => Ok(Self::Other(Decode::decode(input)?)),
DigestItemType::RuntimeEnvironmentUpdated => Ok(Self::RuntimeEnvironmentUpdated),
}
}
}
/// Consensus engine unique ID. From `sp_runtime::ConsensusEngineId`.
pub type ConsensusEngineId = [u8; 4];
impl serde::Serialize for DigestItem {
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.using_encoded(|bytes| impl_serde::serialize::serialize(bytes, seq))
}
}
impl<'a> serde::Deserialize<'a> for DigestItem {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let r = impl_serde::serialize::deserialize(de)?;
Decode::decode(&mut &r[..])
.map_err(|e| serde::de::Error::custom(format!("Decode error: {e}")))
}
}
fn serialize_number<S, T: Copy + Into<U256>>(val: &T, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let u256: U256 = (*val).into();
serde::Serialize::serialize(&u256, s)
}
fn deserialize_number<'a, D, T: TryFrom<U256>>(d: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'a>,
{
// At the time of writing, Smoldot gives back block numbers in numeric rather
// than hex format. So let's support deserializing from both here:
let number_or_hex = NumberOrHex::deserialize(d)?;
let u256 = number_or_hex.into_u256();
TryFrom::try_from(u256).map_err(|_| serde::de::Error::custom("Try from failed"))
}
/// A number type that can be serialized both as a number or a string that encodes a number in a
/// string.
///
/// We allow two representations of the block number as input. Either we deserialize to the type
/// that is specified in the block type or we attempt to parse given hex value.
///
/// The primary motivation for having this type is to avoid overflows when using big integers in
/// JavaScript (which we consider as an important RPC API consumer).
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum NumberOrHex {
/// The number represented directly.
Number(u64),
/// Hex representation of the number.
Hex(U256),
}
impl NumberOrHex {
/// Converts this number into an U256.
pub fn into_u256(self) -> U256 {
match self {
NumberOrHex::Number(n) => n.into(),
NumberOrHex::Hex(h) => h,
}
}
}
impl From<NumberOrHex> for U256 {
fn from(num_or_hex: NumberOrHex) -> U256 {
num_or_hex.into_u256()
}
}
macro_rules! into_number_or_hex {
($($t: ty)+) => {
$(
impl From<$t> for NumberOrHex {
fn from(x: $t) -> Self {
NumberOrHex::Number(x.into())
}
}
)+
}
}
into_number_or_hex!(u8 u16 u32 u64);
impl From<u128> for NumberOrHex {
fn from(n: u128) -> Self {
NumberOrHex::Hex(n.into())
}
}
impl From<U256> for NumberOrHex {
fn from(n: U256) -> Self {
NumberOrHex::Hex(n)
}
}
#[cfg(test)]
mod test {
use super::*;
// Smoldot returns numeric block numbers in the header at the time of writing;
// ensure we can deserialize them properly.
#[test]
fn can_deserialize_numeric_block_number() {
let numeric_block_number_json = r#"
{
"digest": {
"logs": []
},
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": 4,
"parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#;
let header: SubstrateHeader<u32, BlakeTwo256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
// Substrate returns hex block numbers; ensure we can also deserialize those OK.
#[test]
fn can_deserialize_hex_block_number() {
let numeric_block_number_json = r#"
{
"digest": {
"logs": []
},
"extrinsicsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": "0x04",
"parentHash": "0xcb2690b2c85ceab55be03fc7f7f5f3857e7efeb7a020600ebd4331e10be2f7a5",
"stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
"#;
let header: SubstrateHeader<u32, BlakeTwo256> =
serde_json::from_str(numeric_block_number_json).expect("valid block header");
assert_eq!(header.number(), 4);
}
}
-707
View File
@@ -1,707 +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.
//! This module contains implementations for common transaction extensions, each
//! of which implements [`TransactionExtension`], and can be used in conjunction with
//! [`AnyOf`] to configure the set of transaction extensions which are known about
//! when interacting with a chain.
use super::extrinsic_params::ExtrinsicParams;
use crate::client::ClientState;
use crate::config::ExtrinsicParamsEncoder;
use crate::config::{Config, HashFor};
use crate::error::ExtrinsicParamsError;
use crate::utils::{Era, Static};
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::vec::Vec;
use codec::{Compact, Encode};
use core::any::Any;
use core::fmt::Debug;
use derive_where::derive_where;
use hashbrown::HashMap;
use scale_decode::DecodeAsType;
use scale_info::PortableRegistry;
// Re-export this here; it's a bit generically named to be re-exported from ::config.
pub use super::extrinsic_params::Params;
/// A single [`TransactionExtension`] has a unique name, but is otherwise the
/// same as [`ExtrinsicParams`] in describing how to encode the extra and
/// additional data.
pub trait TransactionExtension<T: Config>: ExtrinsicParams<T> {
/// The type representing the `extra` / value bytes of a transaction extension.
/// Decoding from this type should be symmetrical to the respective
/// `ExtrinsicParamsEncoder::encode_value_to()` implementation of this transaction extension.
type Decoded: DecodeAsType;
/// This should return true if the transaction extension matches the details given.
/// Often, this will involve just checking that the identifier given matches that of the
/// extension in question.
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool;
}
/// The [`VerifySignature`] extension. For V5 General transactions, this is how a signature
/// is provided. The signature is constructed by signing a payload which contains the
/// transaction call data as well as the encoded "additional" bytes for any extensions _after_
/// this one in the list.
pub struct VerifySignature<T: Config>(VerifySignatureDetails<T>);
impl<T: Config> ExtrinsicParams<T> for VerifySignature<T> {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(VerifySignature(VerifySignatureDetails::Disabled))
}
}
impl<T: Config> ExtrinsicParamsEncoder for VerifySignature<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
// This extension is never encoded to the signer payload, and extensions
// prior to this are ignored when creating said payload, so clear anything
// we've seen so far.
v.clear();
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
// We only use the "implicit" data for extensions _after_ this one
// in the pipeline to form the signer payload. Thus, clear anything
// we've seen so far.
v.clear();
}
fn inject_signature(&mut self, account: &dyn Any, signature: &dyn Any) {
// Downcast refs back to concrete types (we use `&dyn Any`` so that the trait remains object safe)
let account = account
.downcast_ref::<T::AccountId>()
.expect("A T::AccountId should have been provided")
.clone();
let signature = signature
.downcast_ref::<T::Signature>()
.expect("A T::Signature should have been provided")
.clone();
// The signature is not set through params, only here, once given by a user:
self.0 = VerifySignatureDetails::Signed { signature, account }
}
}
impl<T: Config> TransactionExtension<T> for VerifySignature<T> {
type Decoded = Static<VerifySignatureDetails<T>>;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "VerifySignature"
}
}
/// This allows a signature to be provided to the [`VerifySignature`] transaction extension.
// Dev note: this must encode identically to https://github.com/paritytech/polkadot-sdk/blob/fd72d58313c297a10600037ce1bb88ec958d722e/substrate/frame/verify-signature/src/extension.rs#L43
#[derive(codec::Encode, codec::Decode)]
pub enum VerifySignatureDetails<T: Config> {
/// A signature has been provided.
Signed {
/// The signature.
signature: T::Signature,
/// The account that generated the signature.
account: T::AccountId,
},
/// No signature was provided.
Disabled,
}
/// The [`CheckMetadataHash`] transaction extension.
pub struct CheckMetadataHash {
// Eventually we might provide or calculate the metadata hash here,
// but for now we never provide a hash and so this is empty.
}
impl<T: Config> ExtrinsicParams<T> for CheckMetadataHash {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckMetadataHash {})
}
}
impl ExtrinsicParamsEncoder for CheckMetadataHash {
fn encode_value_to(&self, v: &mut Vec<u8>) {
// A single 0 byte in the TX payload indicates that the chain should
// _not_ expect any metadata hash to exist in the signer payload.
0u8.encode_to(v);
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
// We provide no metadata hash in the signer payload to align with the above.
None::<()>.encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for CheckMetadataHash {
type Decoded = CheckMetadataHashMode;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckMetadataHash"
}
}
/// Is metadata checking enabled or disabled?
// Dev note: The "Disabled" and "Enabled" variant names match those that the
// transaction extension will be encoded with, in order that DecodeAsType will work
// properly.
#[derive(Copy, Clone, Debug, DecodeAsType)]
pub enum CheckMetadataHashMode {
/// No hash was provided in the signer payload.
Disabled,
/// A hash was provided in the signer payload.
Enabled,
}
impl CheckMetadataHashMode {
/// Is metadata checking enabled or disabled for this transaction?
pub fn is_enabled(&self) -> bool {
match self {
CheckMetadataHashMode::Disabled => false,
CheckMetadataHashMode::Enabled => true,
}
}
}
/// The [`CheckSpecVersion`] transaction extension.
pub struct CheckSpecVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
type Params = ();
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckSpecVersion(client.runtime_version.spec_version))
}
}
impl ExtrinsicParamsEncoder for CheckSpecVersion {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for CheckSpecVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckSpecVersion"
}
}
/// The [`CheckNonce`] transaction extension.
pub struct CheckNonce(u64);
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
type Params = CheckNonceParams;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckNonce(params.0.unwrap_or(0)))
}
}
impl ExtrinsicParamsEncoder for CheckNonce {
fn encode_value_to(&self, v: &mut Vec<u8>) {
Compact(self.0).encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for CheckNonce {
type Decoded = u64;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckNonce"
}
}
/// Configure the nonce used.
#[derive(Debug, Clone, Default)]
pub struct CheckNonceParams(Option<u64>);
impl CheckNonceParams {
/// Retrieve the nonce from the chain and use that.
pub fn from_chain() -> Self {
Self(None)
}
/// Manually set an account nonce to use.
pub fn with_nonce(nonce: u64) -> Self {
Self(Some(nonce))
}
}
impl<T: Config> Params<T> for CheckNonceParams {
fn inject_account_nonce(&mut self, nonce: u64) {
if self.0.is_none() {
self.0 = Some(nonce)
}
}
}
/// The [`CheckTxVersion`] transaction extension.
pub struct CheckTxVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
type Params = ();
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckTxVersion(client.runtime_version.transaction_version))
}
}
impl ExtrinsicParamsEncoder for CheckTxVersion {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for CheckTxVersion {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckTxVersion"
}
}
/// The [`CheckGenesis`] transaction extension.
pub struct CheckGenesis<T: Config>(HashFor<T>);
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
type Params = ();
fn new(client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CheckGenesis(client.genesis_hash))
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for CheckGenesis<T> {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckGenesis"
}
}
/// The [`CheckMortality`] transaction extension.
pub struct CheckMortality<T: Config> {
params: CheckMortalityParamsInner<T>,
genesis_hash: HashFor<T>,
}
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
type Params = CheckMortalityParams<T>;
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
// If a user has explicitly configured the transaction to be mortal for n blocks, but we get
// to this stage and no injected information was able to turn this into MortalFromBlock{..},
// then we hit an error as we are unable to construct a mortal transaction here.
if matches!(&params.0, CheckMortalityParamsInner::MortalForBlocks(_)) {
return Err(ExtrinsicParamsError::custom(
"CheckMortality: We cannot construct an offline extrinsic with only the number of blocks it is mortal for. Use mortal_from_unchecked instead.",
));
}
Ok(CheckMortality {
// if nothing has been explicitly configured, we will have a mortal transaction
// valid for 32 blocks if block info is available.
params: params.0,
genesis_hash: client.genesis_hash,
})
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
match &self.params {
CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks,
from_block_n,
..
} => {
Era::mortal(*for_n_blocks, *from_block_n).encode_to(v);
}
_ => {
// Note: if we see `CheckMortalityInner::MortalForBlocks`, then it means the user has
// configured a block to be mortal for N blocks, but the current block was never injected,
// so we don't know where to start from and default back to building an immortal tx.
Era::Immortal.encode_to(v);
}
}
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
match &self.params {
CheckMortalityParamsInner::MortalFromBlock {
from_block_hash, ..
} => {
from_block_hash.encode_to(v);
}
_ => {
self.genesis_hash.encode_to(v);
}
}
}
}
impl<T: Config> TransactionExtension<T> for CheckMortality<T> {
type Decoded = Era;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CheckMortality"
}
}
/// Parameters to configure the [`CheckMortality`] transaction extension.
pub struct CheckMortalityParams<T: Config>(CheckMortalityParamsInner<T>);
enum CheckMortalityParamsInner<T: Config> {
/// The transaction will be immortal.
Immortal,
/// The transaction is mortal for N blocks. This must be "upgraded" into
/// [`CheckMortalityParamsInner::MortalFromBlock`] to ultimately work.
MortalForBlocks(u64),
/// The transaction is mortal for N blocks, but if it cannot be "upgraded",
/// then it will be set to immortal instead. This is the default if unset.
MortalForBlocksOrImmortalIfNotPossible(u64),
/// The transaction is mortal and all of the relevant information is provided.
MortalFromBlock {
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: HashFor<T>,
},
}
impl<T: Config> Default for CheckMortalityParams<T> {
fn default() -> Self {
// default to being mortal for 32 blocks if possible, else immortal:
CheckMortalityParams(CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(32))
}
}
impl<T: Config> CheckMortalityParams<T> {
/// Configure a transaction that will be mortal for the number of blocks given.
pub fn mortal(for_n_blocks: u64) -> Self {
Self(CheckMortalityParamsInner::MortalForBlocks(for_n_blocks))
}
/// Configure a transaction that will be mortal for the number of blocks given,
/// and from the block details provided. Prefer to use [`CheckMortalityParams::mortal()`]
/// where possible, which prevents the block number and hash from being misaligned.
pub fn mortal_from_unchecked(
for_n_blocks: u64,
from_block_n: u64,
from_block_hash: HashFor<T>,
) -> Self {
Self(CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks,
from_block_n,
from_block_hash,
})
}
/// An immortal transaction.
pub fn immortal() -> Self {
Self(CheckMortalityParamsInner::Immortal)
}
}
impl<T: Config> Params<T> for CheckMortalityParams<T> {
fn inject_block(&mut self, from_block_n: u64, from_block_hash: HashFor<T>) {
match &self.0 {
CheckMortalityParamsInner::MortalForBlocks(n)
| CheckMortalityParamsInner::MortalForBlocksOrImmortalIfNotPossible(n) => {
self.0 = CheckMortalityParamsInner::MortalFromBlock {
for_n_blocks: *n,
from_block_n,
from_block_hash,
}
}
_ => {
// Don't change anything if explicit Immortal or explicit block set.
}
}
}
}
/// The [`ChargeAssetTxPayment`] transaction extension.
#[derive(DecodeAsType)]
#[derive_where(Clone, Debug; T::AssetId)]
#[decode_as_type(trait_bounds = "T::AssetId: DecodeAsType")]
pub struct ChargeAssetTxPayment<T: Config> {
tip: Compact<u128>,
asset_id: Option<T::AssetId>,
}
impl<T: Config> ChargeAssetTxPayment<T> {
/// Tip to the extrinsic author in the native chain token.
pub fn tip(&self) -> u128 {
self.tip.0
}
/// Tip to the extrinsic author using the asset ID given.
pub fn asset_id(&self) -> Option<&T::AssetId> {
self.asset_id.as_ref()
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment<T> {
type Params = ChargeAssetTxPaymentParams<T>;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeAssetTxPayment {
tip: Compact(params.tip),
asset_id: params.asset_id,
})
}
}
impl<T: Config> ExtrinsicParamsEncoder for ChargeAssetTxPayment<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
(self.tip, &self.asset_id).encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for ChargeAssetTxPayment<T> {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeAssetTxPayment"
}
}
/// Parameters to configure the [`ChargeAssetTxPayment`] transaction extension.
pub struct ChargeAssetTxPaymentParams<T: Config> {
tip: u128,
asset_id: Option<T::AssetId>,
}
impl<T: Config> Default for ChargeAssetTxPaymentParams<T> {
fn default() -> Self {
ChargeAssetTxPaymentParams {
tip: Default::default(),
asset_id: Default::default(),
}
}
}
impl<T: Config> ChargeAssetTxPaymentParams<T> {
/// Don't provide a tip to the extrinsic author.
pub fn no_tip() -> Self {
ChargeAssetTxPaymentParams {
tip: 0,
asset_id: None,
}
}
/// Tip the extrinsic author in the native chain token.
pub fn tip(tip: u128) -> Self {
ChargeAssetTxPaymentParams {
tip,
asset_id: None,
}
}
/// Tip the extrinsic author using the asset ID given.
pub fn tip_of(tip: u128, asset_id: T::AssetId) -> Self {
ChargeAssetTxPaymentParams {
tip,
asset_id: Some(asset_id),
}
}
}
impl<T: Config> Params<T> for ChargeAssetTxPaymentParams<T> {}
/// The [`ChargeTransactionPayment`] transaction extension.
#[derive(Clone, Debug, DecodeAsType)]
pub struct ChargeTransactionPayment {
tip: Compact<u128>,
}
impl ChargeTransactionPayment {
/// Tip to the extrinsic author in the native chain token.
pub fn tip(&self) -> u128 {
self.tip.0
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
type Params = ChargeTransactionPaymentParams;
fn new(_client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(ChargeTransactionPayment {
tip: Compact(params.tip),
})
}
}
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
fn encode_value_to(&self, v: &mut Vec<u8>) {
self.tip.encode_to(v);
}
}
impl<T: Config> TransactionExtension<T> for ChargeTransactionPayment {
type Decoded = Self;
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "ChargeTransactionPayment"
}
}
/// Parameters to configure the [`ChargeTransactionPayment`] transaction extension.
#[derive(Default)]
pub struct ChargeTransactionPaymentParams {
tip: u128,
}
impl ChargeTransactionPaymentParams {
/// Don't provide a tip to the extrinsic author.
pub fn no_tip() -> Self {
ChargeTransactionPaymentParams { tip: 0 }
}
/// Tip the extrinsic author in the native chain token.
pub fn tip(tip: u128) -> Self {
ChargeTransactionPaymentParams { tip }
}
}
impl<T: Config> Params<T> for ChargeTransactionPaymentParams {}
/// This accepts a tuple of [`TransactionExtension`]s, and will dynamically make use of whichever
/// ones are actually required for the chain in the correct order, ignoring the rest. This
/// is a sensible default, and allows for a single configuration to work across multiple chains.
pub struct AnyOf<T, Params> {
params: Vec<Box<dyn ExtrinsicParamsEncoder + Send + 'static>>,
_marker: core::marker::PhantomData<(T, Params)>,
}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
// We do some magic when the tuple is wrapped in AnyOf. We
// look at the metadata, and use this to select and make use of only the extensions
// that we actually need for the chain we're dealing with.
impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: TransactionExtension<T>,)+
{
type Params = ($($ident::Params,)+);
fn new(
client: &ClientState<T>,
params: Self::Params,
) -> Result<Self, ExtrinsicParamsError> {
let metadata = &client.metadata;
let types = metadata.types();
// For each transaction extension in the tuple, find the matching index in the metadata, if
// there is one, and add it to a map with that index as the key.
let mut exts_by_index = HashMap::new();
$({
for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
// Skip over any exts that have a match already:
if exts_by_index.contains_key(&idx) {
continue
}
// Break and record as soon as we find a match:
if $ident::matches(e.identifier(), e.extra_ty(), types) {
let ext = $ident::new(client, params.$index)?;
let boxed_ext: Box<dyn ExtrinsicParamsEncoder + Send + 'static> = Box::new(ext);
exts_by_index.insert(idx, boxed_ext);
break
}
}
})+
// Next, turn these into an ordered vec, erroring if we haven't matched on any exts yet.
let mut params = Vec::new();
for (idx, e) in metadata.extrinsic().transaction_extensions_to_use_for_encoding().enumerate() {
let Some(ext) = exts_by_index.remove(&idx) else {
if is_type_empty(e.extra_ty(), types) {
continue
} else {
return Err(ExtrinsicParamsError::UnknownTransactionExtension(e.identifier().to_owned()));
}
};
params.push(ext);
}
Ok(AnyOf {
params,
_marker: core::marker::PhantomData
})
}
}
impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: TransactionExtension<T>,)+
{
fn encode_value_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_value_to(v);
}
}
fn encode_signer_payload_value_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_signer_payload_value_to(v);
}
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_implicit_to(v);
}
}
fn inject_signature(&mut self, account_id: &dyn Any, signature: &dyn Any) {
for ext in &mut self.params {
ext.inject_signature(account_id, signature);
}
}
}
}
}
#[rustfmt::skip]
const _: () = {
impl_tuples!(A 0);
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);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20);
};
/// Checks to see whether the type being given is empty, ie would require
/// 0 bytes to encode.
fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool {
let Some(ty) = types.resolve(type_id) else {
// Can't resolve; type may not be empty. Not expected to hit this.
return false;
};
use scale_info::TypeDef;
match &ty.type_def {
TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)),
TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types),
TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)),
// Explicitly list these in case any additions are made in the future.
TypeDef::BitSequence(_)
| TypeDef::Variant(_)
| TypeDef::Sequence(_)
| TypeDef::Compact(_)
| TypeDef::Primitive(_) => false,
}
}
-138
View File
@@ -1,138 +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.
//! Construct addresses to access constants with.
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: DecodeAsType;
/// The name of the pallet that the constant lives under.
fn pallet_name(&self) -> &str;
/// The name of the constant in a given pallet.
fn constant_name(&self) -> &str;
/// An optional hash which, if present, will be checked against
/// the node metadata to confirm that the return type matches what
/// we are expecting.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
// 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 StaticAddress<ReturnTy> {
pallet_name: Cow<'static, str>,
constant_name: Cow<'static, str>,
constant_hash: Option<[u8; 32]>,
_marker: core::marker::PhantomData<ReturnTy>,
}
/// A dynamic lookup address to access a constant.
pub type DynamicAddress<ReturnTy> = StaticAddress<ReturnTy>;
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()),
constant_name: Cow::Owned(constant_name.into()),
constant_hash: None,
_marker: core::marker::PhantomData,
}
}
/// Create a new [`StaticAddress`] that will be validated
/// against node metadata using the hash given.
#[doc(hidden)]
pub fn new_static(
pallet_name: &'static str,
constant_name: &'static str,
hash: [u8; 32],
) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
constant_name: Cow::Borrowed(constant_name),
constant_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this constant prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
pallet_name: self.pallet_name,
constant_name: self.constant_name,
constant_hash: None,
_marker: self._marker,
}
}
}
impl<ReturnTy: DecodeAsType> Address for StaticAddress<ReturnTy> {
type Target = ReturnTy;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn constant_name(&self) -> &str {
&self.constant_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.constant_hash
}
}
/// Construct a new dynamic constant lookup.
pub fn dynamic<ReturnTy: DecodeAsType>(
pallet_name: impl Into<String>,
constant_name: impl Into<String>,
) -> DynamicAddress<ReturnTy> {
DynamicAddress::new(pallet_name, constant_name)
}
-109
View File
@@ -1,109 +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.
//! Access constants from metadata.
//!
//! Use [`get`] to retrieve a constant from some metadata, or [`validate`] to check that a static
//! constant address lines up with the value seen in the metadata.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::constants;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // 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();
//!
//! // We can use a static address to obtain some constant:
//! let address = polkadot::constants().balances().existential_deposit();
//!
//! // This validates that the address given is in line with the metadata
//! // we're trying to access the constant in:
//! constants::validate(&address, &metadata).expect("is valid");
//!
//! // This acquires the constant (and internally also validates it):
//! let ed = constants::get(&address, &metadata).expect("can decode constant");
//!
//! assert_eq!(ed, 33_333_333);
//! ```
pub mod address;
use crate::Metadata;
use crate::error::ConstantError;
use address::Address;
use alloc::borrow::ToOwned;
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<(), ConstantError> {
if let Some(actual_hash) = address.validation_hash() {
let expected_hash = metadata
.pallet_by_name(address.pallet_name())
.ok_or_else(|| ConstantError::PalletNameNotFound(address.pallet_name().to_string()))?
.constant_hash(address.constant_name())
.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(ConstantError::IncompatibleCodegen);
}
}
Ok(())
}
/// 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, ConstantError> {
// 1. Validate constant shape if hash given:
validate(&address, metadata)?;
// 2. Attempt to decode the constant into the type given:
let constant = frame_decode::constants::decode_constant(
address.pallet_name(),
address.constant_name(),
metadata,
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())
}
-117
View File
@@ -1,117 +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.
//! Construct addresses to access custom values with.
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::{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: DecodeAsType;
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
/// 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;
/// An optional hash which, if present, can be checked against node metadata.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
// 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 = scale_value::Value;
type IsDecodable = Maybe;
fn name(&self) -> &str {
self
}
}
/// A static address to a custom value.
#[derive_where(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct StaticAddress<ReturnTy, IsDecodable> {
name: Cow<'static, str>,
hash: Option<[u8; 32]>,
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]) -> Self {
Self {
name: Cow::Borrowed(name),
hash: Some(hash),
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,
}
}
/// Do not validate this custom value prior to accessing it.
pub fn unvalidated(self) -> Self {
Self {
name: self.name,
hash: None,
marker: self.marker,
}
}
}
impl<Target: DecodeAsType, IsDecodable: NoMaybe> Address for StaticAddress<Target, IsDecodable> {
type Target = Target;
type IsDecodable = IsDecodable;
fn name(&self) -> &str {
&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)
}
-179
View File
@@ -1,179 +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.
//! Access custom values from metadata.
//!
//! Use [`get`] to retrieve a custom value from some metadata, or [`validate`] to check that a
//! static custom value address lines up with the value seen in the metadata.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::custom_values;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // 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();
//!
//! // At the moment, we don't expect to see any custom values in the metadata
//! // for Polkadot, so this will return an error:
//! let err = custom_values::get("Foo", &metadata);
//! ```
pub mod address;
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>(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(|| CustomValueError::NotFound(address.name().into()))?;
let expected_hash = custom_value.hash();
if actual_hash != expected_hash {
return Err(CustomValueError::IncompatibleCodegen);
}
}
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 = Maybe>>(
address: Addr,
metadata: &Metadata,
) -> Result<Addr::Target, CustomValueError> {
// 1. Validate custom value shape if hash given:
validate(&address, metadata)?;
// 2. Attempt to decode custom value:
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>(
address: Addr,
metadata: &Metadata,
) -> Result<Vec<u8>, CustomValueError> {
// 1. Validate custom value shape if hash given:
validate(&address, metadata)?;
// 2. Return the underlying bytes:
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)]
mod tests {
use super::*;
use alloc::collections::BTreeMap;
use codec::Encode;
use scale_decode::DecodeAsType;
use scale_info::TypeInfo;
use scale_info::form::PortableForm;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec;
use crate::custom_values;
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)]
pub struct Person {
age: u16,
name: String,
}
fn mock_metadata() -> Metadata {
let person_ty = scale_info::MetaType::new::<Person>();
let unit = scale_info::MetaType::new::<()>();
let mut types = scale_info::Registry::new();
let person_ty_id = types.register_type(&person_ty);
let unit_id = types.register_type(&unit);
let types: scale_info::PortableRegistry = types.into();
let person = Person {
age: 42,
name: "Neo".into(),
};
let person_value_metadata: frame_metadata::v15::CustomValueMetadata<PortableForm> =
frame_metadata::v15::CustomValueMetadata {
ty: person_ty_id,
value: person.encode(),
};
let frame_metadata = frame_metadata::v15::RuntimeMetadataV15 {
types,
pallets: vec![],
extrinsic: frame_metadata::v15::ExtrinsicMetadata {
version: 0,
address_ty: unit_id,
call_ty: unit_id,
signature_ty: unit_id,
extra_ty: unit_id,
signed_extensions: vec![],
},
ty: unit_id,
apis: vec![],
outer_enums: frame_metadata::v15::OuterEnums {
call_enum_ty: unit_id,
event_enum_ty: unit_id,
error_enum_ty: unit_id,
},
custom: frame_metadata::v15::CustomMetadata {
map: BTreeMap::from_iter([("Mr. Robot".to_owned(), person_value_metadata)]),
},
};
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
metadata
}
#[test]
fn test_decoding() {
let metadata = mock_metadata();
assert!(custom_values::get("Invalid Address", &metadata).is_err());
let person_addr = custom_values::address::dynamic::<Person>("Mr. Robot");
let person = custom_values::get(&person_addr, &metadata).unwrap();
assert_eq!(
person,
Person {
age: 42,
name: "Neo".into()
}
)
}
}
-319
View File
@@ -1,319 +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.
//! The errors that can be emitted in this crate.
use alloc::boxed::Box;
use alloc::string::String;
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 {
#[error(transparent)]
StorageError(#[from] StorageError),
#[error(transparent)]
Extrinsic(#[from] ExtrinsicError),
#[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),
}
#[derive(Debug, DeriveError)]
#[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"
)]
LeftoverBytes {
/// Index of the extrinsic that failed to decode.
extrinsic_index: usize,
/// Number of bytes leftover after decoding the extrinsic.
num_leftover_bytes: usize,
},
#[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: 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,
},
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
#[error("Cannot decode extrinsic at index {extrinsic_index}: {error}")]
pub struct ExtrinsicDecodeErrorAt {
pub extrinsic_index: usize,
pub error: ExtrinsicDecodeErrorAtReason,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[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 {
#[error("Cannot find type id '{type_id} in the metadata (context: {context})")]
MissingTypeId {
/// Type ID.
type_id: u32,
/// Some arbitrary context to help narrow the source of the error.
context: &'static str,
},
#[error("The chain expects a signed extension with the name {0}, but we did not provide one")]
UnknownTransactionExtension(String),
#[error("Error constructing extrinsic parameters: {0}")]
Custom(Box<dyn core::error::Error + Send + Sync + 'static>),
}
impl ExtrinsicParamsError {
/// Create a custom [`ExtrinsicParamsError`] from a string.
pub fn custom<S: Into<String>>(error: S) -> Self {
let error: String = error.into();
let error: Box<dyn core::error::Error + Send + Sync + 'static> = Box::from(error);
ExtrinsicParamsError::Custom(error)
}
}
impl From<core::convert::Infallible> for ExtrinsicParamsError {
fn from(value: core::convert::Infallible) -> Self {
match value {}
}
}
-1022
View File
File diff suppressed because it is too large Load Diff
-49
View File
@@ -1,49 +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.
//! # subxt-core
//!
//! A `#[no_std]` compatible subset of the functionality provided in the `subxt` crate. This
//! contains the core logic for encoding and decoding things, but nothing related to networking.
//!
//! Here's an overview of the main things exposed here:
//!
//! - [`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.
//! - [`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.
//! - [`events`]: decode and explore events.
//!
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
pub extern crate alloc;
pub mod blocks;
pub mod client;
pub mod config;
pub mod constants;
pub mod custom_values;
pub mod dynamic;
pub mod error;
pub mod events;
pub mod runtime_api;
pub mod storage;
pub mod tx;
pub mod utils;
pub mod view_functions;
pub use config::Config;
pub use error::Error;
pub use subxt_metadata::Metadata;
/// Re-exports of some of the key external crates.
pub mod ext {
pub use codec;
pub use scale_decode;
pub use scale_encode;
pub use scale_value;
}
-120
View File
@@ -1,120 +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.
//! Encode runtime API payloads, decode the associated values returned from them, and validate
//! static runtime API payloads.
//!
//! # Example
//!
//! ```rust
//! use subxt_macro::subxt;
//! use subxt_core::runtime_api;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // 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();
//!
//! // Build a storage query to access account information.
//! let payload = polkadot::apis().metadata().metadata_versions();
//!
//! // We can validate that the payload is compatible with the given metadata.
//! runtime_api::validate(&payload, &metadata).unwrap();
//!
//! // Encode the payload name and arguments to hand to a node:
//! let _call_name = runtime_api::call_name(&payload);
//! let _call_args = runtime_api::call_args(&payload, &metadata).unwrap();
//!
//! // If we were to obtain a value back from the node, we could
//! // then decode it using the same payload and metadata like so:
//! let value_bytes = hex::decode("080e0000000f000000").unwrap();
//! let value = runtime_api::decode_value(&mut &*value_bytes, &payload, &metadata).unwrap();
//!
//! println!("Available metadata versions: {value:?}");
//! ```
pub mod payload;
use crate::Metadata;
use crate::error::RuntimeApiError;
use alloc::format;
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<(), RuntimeApiError> {
let Some(hash) = payload.validation_hash() else {
return Ok(());
};
let trait_name = payload.trait_name();
let method_name = payload.method_name();
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(())
}
}
/// Return the name of the runtime API call from the payload.
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>, 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,
metadata: &Metadata,
) -> 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(value)
}
-162
View File
@@ -1,162 +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.
//! This module contains the trait and types used to represent
//! runtime API calls that can be made.
use alloc::borrow::Cow;
use alloc::string::String;
use core::marker::PhantomData;
use derive_where::derive_where;
use frame_decode::runtime_apis::IntoEncodableValues;
use scale_decode::DecodeAsType;
/// 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.
type ReturnType: DecodeAsType;
/// The runtime API trait name.
fn trait_name(&self) -> &str;
/// The runtime API method name.
fn method_name(&self) -> &str;
/// The input arguments.
fn args(&self) -> &Self::ArgsType;
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
// 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; ArgsType)]
pub struct StaticPayload<ArgsType, ReturnType> {
trait_name: Cow<'static, str>,
method_name: Cow<'static, str>,
args: ArgsType,
validation_hash: Option<[u8; 32]>,
_marker: PhantomData<ReturnType>,
}
/// A dynamic runtime API payload.
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
type ArgsType = ArgsType;
type ReturnType = ReturnType;
fn trait_name(&self) -> &str {
&self.trait_name
}
fn method_name(&self) -> &str {
&self.method_name
}
fn args(&self) -> &Self::ArgsType {
&self.args
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
impl<ArgsType, ReturnTy> StaticPayload<ArgsType, ReturnTy> {
/// Create a new [`StaticPayload`].
pub fn new(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args: ArgsType,
) -> Self {
StaticPayload {
trait_name: trait_name.into().into(),
method_name: method_name.into().into(),
args,
validation_hash: None,
_marker: PhantomData,
}
}
/// Create a new static [`StaticPayload`] using static function name
/// and scale-encoded argument data.
///
/// This is only expected to be used from codegen.
#[doc(hidden)]
pub fn new_static(
trait_name: &'static str,
method_name: &'static str,
args: ArgsType,
hash: [u8; 32],
) -> StaticPayload<ArgsType, ReturnTy> {
StaticPayload {
trait_name: Cow::Borrowed(trait_name),
method_name: Cow::Borrowed(method_name),
args,
validation_hash: Some(hash),
_marker: core::marker::PhantomData,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the trait name.
pub fn trait_name(&self) -> &str {
&self.trait_name
}
/// Returns the method name.
pub fn method_name(&self) -> &str {
&self.method_name
}
}
/// Create a new [`DynamicPayload`].
pub fn dynamic<ArgsType, ReturnType>(
trait_name: impl Into<String>,
method_name: impl Into<String>,
args_data: ArgsType,
) -> DynamicPayload<ArgsType, ReturnType> {
DynamicPayload::new(trait_name, method_name, args_data)
}
-171
View File
@@ -1,171 +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.
//! Construct addresses to access storage entries with.
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;
/// 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 {
/// 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 pallet containing this storage entry.
fn pallet_name(&self) -> &str;
/// The name of the storage entry.
fn entry_name(&self) -> &str;
/// 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)
}
fn validation_hash(&self) -> Option<[u8; 32]> {
A::validation_hash(*self)
}
}
/// 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>,
validation_hash: Option<[u8; 32]>,
marker: core::marker::PhantomData<(KeyParts, Value, IsPlain)>,
}
impl<KeyParts, Value, IsPlain> Clone for StaticAddress<KeyParts, Value, IsPlain> {
fn clone(&self) -> Self {
Self {
pallet_name: self.pallet_name.clone(),
entry_name: self.entry_name.clone(),
validation_hash: self.validation_hash,
marker: self.marker,
}
}
}
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, hash: [u8; 32]) -> Self {
Self {
pallet_name: Cow::Borrowed(pallet_name),
entry_name: Cow::Borrowed(entry_name),
validation_hash: Some(hash),
marker: core::marker::PhantomData,
}
}
/// 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,
marker: core::marker::PhantomData,
}
}
/// Do not validate this storage entry prior to accessing it.
pub fn unvalidated(mut self) -> Self {
self.validation_hash = None;
self
}
}
impl<KeyParts, Value, IsPlain> Address for StaticAddress<KeyParts, Value, IsPlain>
where
KeyParts: IntoEncodableValues + IntoDecodableValues,
Value: DecodeAsType,
IsPlain: YesMaybe,
{
type KeyParts = KeyParts;
type Value = Value;
type IsPlain = IsPlain;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn entry_name(&self) -> &str {
&self.entry_name
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
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>,
) -> DynamicAddress<KeyParts, Value> {
DynamicAddress::<KeyParts, Value>::new(pallet_name.into(), entry_name.into())
}
-94
View File
@@ -1,94 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Encode storage keys, decode storage values, and validate static storage addresses.
//!
//! # Example
//!
//! ```rust
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::storage;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // 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();
//!
//! // Build a storage query to access account information.
//! let address = polkadot::storage().system().account();
//!
//! // We can validate that the address is compatible with the given metadata.
//! storage::validate(&address, &metadata).unwrap();
//!
//! // We can fetch details about the storage entry associated with this address:
//! let entry = storage::entry(address, &metadata).unwrap();
//!
//! // .. 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 = entry.value(value_bytes).decode().unwrap();
//!
//! println!("Alice's account info: {value:?}");
//! ```
mod prefix_of;
mod storage_entry;
mod storage_key;
mod storage_key_value;
mod storage_value;
pub mod address;
use crate::{Metadata, error::StorageError};
use address::Address;
use alloc::string::ToString;
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<(), StorageError> {
let Some(hash) = address.validation_hash() else {
return Ok(());
};
let pallet_name = address.pallet_name();
let entry_name = address.entry_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(),
}
})?;
if storage_hash != hash {
Err(StorageError::IncompatibleCodegen)
} else {
Ok(())
}
}
-195
View File
@@ -1,195 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use 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
@@ -1,155 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use 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)
}
}
}
-138
View File
@@ -1,138 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::error::StorageKeyError;
use alloc::sync::Arc;
use core::marker::PhantomData;
use frame_decode::storage::{IntoDecodableValues, StorageInfo, StorageKey as StorageKeyPartInfo};
use scale_info::PortableRegistry;
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<'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 !cursor.is_empty() {
return Err(StorageKeyError::LeftoverBytes {
bytes: cursor.to_vec(),
});
}
Ok(StorageKey {
info: Arc::new(storage_key_info),
types,
bytes,
marker: PhantomData,
})
}
/// 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)?;
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
}
}
}
/// This represents a part of a storage key.
pub struct StorageKeyPart<'info> {
index: usize,
info: Arc<StorageKeyPartInfo<u32>>,
types: &'info PortableRegistry,
bytes: Arc<[u8]>,
}
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,
};
&self.bytes[combined_range]
}
/// Get the hasher that was used to construct this part of the storage key.
pub fn hasher(&self) -> StorageHasher {
self.info[self.index].hasher()
}
/// 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);
};
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
@@ -1,49 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use 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
@@ -1,71 +0,0 @@
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::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)
}
}
-458
View File
@@ -1,458 +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.
//! Construct and sign transactions.
//!
//! # Example
//!
//! ```rust
//! use subxt_signer::sr25519::dev;
//! use subxt_macro::subxt;
//! use subxt_core::config::{PolkadotConfig, HashFor};
//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
//! use subxt_core::tx;
//! use subxt_core::utils::H256;
//! use subxt_core::Metadata;
//!
//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
//! #[subxt(
//! crate = "::subxt_core",
//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
//! )]
//! pub mod polkadot {}
//!
//! // Gather some other information about the chain that we'll need to construct valid extrinsics:
//! let state = tx::ClientState::<PolkadotConfig> {
//! metadata: {
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
//! Metadata::decode_from(&metadata_bytes[..]).unwrap()
//! },
//! genesis_hash: {
//! let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
//! let bytes = hex::decode(h).unwrap();
//! H256::from_slice(&bytes)
//! },
//! runtime_version: tx::RuntimeVersion {
//! spec_version: 9370,
//! transaction_version: 20,
//! }
//! };
//!
//! // Now we can build a balance transfer extrinsic.
//! let dest = dev::bob().public_key().into();
//! let call = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
//! let params = Params::new().tip(1_000).nonce(0).build();
//!
//! // We can validate that this lines up with the given metadata:
//! tx::validate(&call, &state.metadata).unwrap();
//!
//! // We can build a signed transaction:
//! let signed_call = tx::create_v4_signed(&call, &state, params)
//! .unwrap()
//! .sign(&dev::alice());
//!
//! // And log it:
//! println!("Tx: 0x{}", hex::encode(signed_call.encoded()));
//! ```
pub mod payload;
pub mod signer;
use crate::Metadata;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
use crate::error::ExtrinsicError;
use crate::utils::Encoded;
use alloc::borrow::Cow;
use alloc::string::ToString;
use alloc::vec::Vec;
use codec::{Compact, Encode};
use payload::Payload;
use signer::Signer as SignerT;
use sp_crypto_hashing::blake2_256;
// Expose these here since we expect them in some calls below.
pub use crate::client::{ClientState, RuntimeVersion};
/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
/// if the call is valid (or if it's not possible to check since the call has no validation hash).
/// Return an error if the call was not valid or something went wrong trying to validate it (ie
/// the pallet or call in question do not exist at all).
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), ExtrinsicError> {
let Some(details) = call.validation_details() else {
return Ok(());
};
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(())
}
}
/// Returns the suggested transaction versions to build for a given chain, or an error
/// if Subxt doesn't support any version expected by the chain.
///
/// 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, ExtrinsicError> {
let versions = metadata.extrinsic().supported_versions();
if versions.contains(&4) {
Ok(TransactionVersion::V4)
} else if versions.contains(&5) {
Ok(TransactionVersion::V5)
} else {
Err(ExtrinsicError::UnsupportedVersion)
}
}
/// The transaction versions supported by Subxt.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum TransactionVersion {
/// v4 transactions (signed and unsigned transactions)
V4,
/// v5 transactions (bare and general transactions)
V5,
}
/// 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>, ExtrinsicError> {
let mut bytes = Vec::new();
call.encode_call_data_to(metadata, &mut bytes)?;
Ok(bytes)
}
/// Creates a V4 "unsigned" transaction without submitting it.
pub fn create_v4_unsigned<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 4, metadata)
}
/// Creates a V5 "bare" transaction without submitting it.
pub fn create_v5_bare<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, ExtrinsicError> {
create_unsigned_at_version(call, 5, metadata)
}
// Create a V4 "unsigned" transaction or V5 "bare" transaction.
fn create_unsigned_at_version<T: Config, Call: Payload>(
call: &Call,
tx_version: u8,
metadata: &Metadata,
) -> 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)?;
// 2. Encode extrinsic
let extrinsic = {
let mut encoded_inner = Vec::new();
// encode the transaction version first.
tx_version.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data_to(metadata, &mut encoded_inner)?;
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
Ok(Transaction::from_bytes(extrinsic))
}
/// Construct a v4 extrinsic, ready to be signed.
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>, 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)?;
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = call_data(call, &client_state.metadata)?;
// 3. Construct our custom additional/extra params.
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialTransactionV4 {
call_data,
additional_and_extra_params,
})
}
/// Construct a v5 "general" extrinsic, ready to be signed or emitted as is.
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>, 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)?;
// 2. Work out which TX extension version to target based on metadata.
let tx_extensions_version = client_state
.metadata
.extrinsic()
.transaction_extension_version_to_use_for_encoding();
// 3. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = call_data(call, &client_state.metadata)?;
// 4. Construct our custom additional/extra params.
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialTransactionV5 {
call_data,
additional_and_extra_params,
tx_extensions_version,
})
}
/// A partially constructed V4 extrinsic, ready to be signed.
pub struct PartialTransactionV4<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
}
impl<T: Config> PartialTransactionV4<T> {
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
// Obtain bytes representing the signer payload and run call some function
// with them. This can avoid an allocation in some cases.
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params
.encode_signer_payload_value_to(&mut bytes);
self.additional_and_extra_params
.encode_implicit_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(&blake2_256(&bytes)))
} else {
f(Cow::Owned(bytes))
}
}
/// Return the V4 signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
}
/// Convert this [`PartialTransactionV4`] into a V4 signed [`Transaction`], ready to submit.
/// The provided `signer` is responsible for providing the "from" address for the transaction,
/// as well as providing a signature to attach to it.
pub fn sign<Signer>(&self, signer: &Signer) -> Transaction<T>
where
Signer: SignerT<T>,
{
// Given our signer, we can sign the payload representing this extrinsic.
let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
// Now, use the signature and "from" address to build the extrinsic.
self.sign_with_account_and_signature(signer.account_id(), &signature)
}
/// Convert this [`PartialTransactionV4`] into a V4 signed [`Transaction`], ready to submit.
/// The provided `address` and `signature` will be used.
pub fn sign_with_account_and_signature(
&self,
account_id: T::AccountId,
signature: &T::Signature,
) -> Transaction<T> {
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
let address: T::Address = account_id.into();
address.encode_to(&mut encoded_inner);
// the signature
signature.encode_to(&mut encoded_inner);
// attach custom extra params
self.additional_and_extra_params
.encode_value_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Return an extrinsic ready to be submitted.
Transaction::from_bytes(extrinsic)
}
}
/// A partially constructed V5 general extrinsic, ready to be signed or emitted as-is.
pub struct PartialTransactionV5<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
tx_extensions_version: u8,
}
impl<T: Config> PartialTransactionV5<T> {
/// Return the bytes representing the call data for this partially constructed
/// extrinsic.
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
/// Return the V5 signer payload for this extrinsic. These are the bytes that must
/// be signed in order to produce a valid signature for the extrinsic.
pub fn signer_payload(&self) -> [u8; 32] {
let mut bytes = self.call_data.clone();
self.additional_and_extra_params
.encode_signer_payload_value_to(&mut bytes);
self.additional_and_extra_params
.encode_implicit_to(&mut bytes);
blake2_256(&bytes)
}
/// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`].
///
/// This transaction has not been explicitly signed. Use [`Self::sign`]
/// or [`Self::sign_with_account_and_signature`] if you wish to provide a
/// signature (this is usually a necessary step).
pub fn to_transaction(&self) -> Transaction<T> {
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is general" + transaction protocol version (5)
(0b01000000 + 5u8).encode_to(&mut encoded_inner);
// Encode versions for the transaction extensions
self.tx_extensions_version.encode_to(&mut encoded_inner);
// Encode the actual transaction extensions values
self.additional_and_extra_params
.encode_value_to(&mut encoded_inner);
// and now, call data (remembering that it's been encoded already and just needs appending)
encoded_inner.extend(&self.call_data);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Return an extrinsic ready to be submitted.
Transaction::from_bytes(extrinsic)
}
/// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`] with a signature.
///
/// Signing the transaction injects the signature into the transaction extension data, which is why
/// this method borrows self mutably. Signing repeatedly will override the previous signature.
pub fn sign<Signer>(&mut self, signer: &Signer) -> Transaction<T>
where
Signer: SignerT<T>,
{
// Given our signer, we can sign the payload representing this extrinsic.
let signature = signer.sign(&self.signer_payload());
// Now, use the signature and "from" account to build the extrinsic.
self.sign_with_account_and_signature(&signer.account_id(), &signature)
}
/// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`] with a signature.
/// Prefer [`Self::sign`] if you have a [`SignerT`] instance to use.
///
/// Signing the transaction injects the signature into the transaction extension data, which is why
/// this method borrows self mutably. Signing repeatedly will override the previous signature.
pub fn sign_with_account_and_signature(
&mut self,
account_id: &T::AccountId,
signature: &T::Signature,
) -> Transaction<T> {
// Inject the signature into the transaction extensions
// before constructing it.
self.additional_and_extra_params
.inject_signature(account_id, signature);
self.to_transaction()
}
}
/// This represents a signed transaction that's ready to be submitted.
/// Use [`Transaction::encoded()`] or [`Transaction::into_encoded()`] to
/// get the bytes for it, or [`Transaction::hash_with()`] to hash the transaction
/// given an instance of [`Config::Hasher`].
pub struct Transaction<T> {
encoded: Encoded,
marker: core::marker::PhantomData<T>,
}
impl<T: Config> Transaction<T> {
/// Create a [`Transaction`] from some already-signed and prepared
/// extrinsic bytes,
pub fn from_bytes(tx_bytes: Vec<u8>) -> Self {
Self {
encoded: Encoded(tx_bytes),
marker: core::marker::PhantomData,
}
}
/// Calculate and return the hash of the extrinsic, based on the provided hasher.
/// If you don't have a hasher to hand, you can construct one using the metadata
/// with `T::Hasher::new(&metadata)`. This will create a hasher suitable for the
/// current chain where possible.
pub fn hash_with(&self, hasher: T::Hasher) -> HashFor<T> {
hasher.hash_of(&self.encoded)
}
/// Returns the SCALE encoded extrinsic bytes.
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
/// Consumes this [`Transaction`] and returns the SCALE encoded
/// extrinsic bytes.
pub fn into_encoded(self) -> Vec<u8> {
self.encoded.0
}
}
-279
View File
@@ -1,279 +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.
//! This module contains the trait and types used to represent
//! transactions that can be submitted.
use crate::Metadata;
use crate::error::ExtrinsicError;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use codec::Encode;
use scale_encode::EncodeAsFields;
use scale_value::{Composite, Value, ValueDef, Variant};
/// This represents a transaction payload that can be submitted
/// 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<(), 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>, ExtrinsicError> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}
/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
None
}
}
macro_rules! boxed_payload {
($ty:path) => {
impl<T: Payload + ?Sized> Payload for $ty {
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), ExtrinsicError> {
self.as_ref().encode_call_data_to(metadata, out)
}
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, ExtrinsicError> {
self.as_ref().encode_call_data(metadata)
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.as_ref().validation_details()
}
}
};
}
boxed_payload!(Box<T>);
#[cfg(feature = "std")]
boxed_payload!(std::sync::Arc<T>);
#[cfg(feature = "std")]
boxed_payload!(std::rc::Rc<T>);
/// Details required to validate the shape of a transaction payload against some metadata.
pub struct ValidationDetails<'a> {
/// The pallet name.
pub pallet_name: &'a str,
/// The call name.
pub call_name: &'a str,
/// A hash (this is generated at compile time in our codegen)
/// to compare against the runtime code.
pub hash: [u8; 32],
}
/// A transaction payload containing some generic `CallData`.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct DefaultPayload<CallData> {
pallet_name: Cow<'static, str>,
call_name: Cow<'static, str>,
call_data: CallData,
validation_hash: Option<[u8; 32]>,
}
/// The payload type used by static codegen.
pub type StaticPayload<Calldata> = DefaultPayload<Calldata>;
/// The type of a payload typically used for dynamic transaction payloads.
pub type DynamicPayload = DefaultPayload<Composite<()>>;
impl<CallData> DefaultPayload<CallData> {
/// Create a new [`DefaultPayload`].
pub fn new(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: CallData,
) -> Self {
DefaultPayload {
pallet_name: Cow::Owned(pallet_name.into()),
call_name: Cow::Owned(call_name.into()),
call_data,
validation_hash: None,
}
}
/// Create a new [`DefaultPayload`] 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,
call_name: &'static str,
call_data: CallData,
validation_hash: [u8; 32],
) -> Self {
DefaultPayload {
pallet_name: Cow::Borrowed(pallet_name),
call_name: Cow::Borrowed(call_name),
call_data,
validation_hash: Some(validation_hash),
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
/// Returns the call data.
pub fn call_data(&self) -> &CallData {
&self.call_data
}
/// Returns the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// Returns the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}
}
impl DefaultPayload<Composite<()>> {
/// Convert the dynamic `Composite` payload into a [`Value`].
/// This is useful if you want to use this as an argument for a
/// larger dynamic call that wants to use this as a nested call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.call_data,
}),
};
Value::unnamed_variant(self.pallet_name, [call])
}
}
impl<CallData: EncodeAsFields> Payload for DefaultPayload<CallData> {
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(|| ExtrinsicError::CallNameNotFound {
pallet_name: pallet.name().to_string(),
call_name: self.call_name.to_string(),
})?;
let pallet_index = pallet.call_index();
let call_index = call.index;
pallet_index.encode_to(out);
call_index.encode_to(out);
let mut fields = call
.fields
.iter()
.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)
.map_err(ExtrinsicError::CannotEncodeCallData)?;
Ok(())
}
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
self.validation_hash.map(|hash| ValidationDetails {
pallet_name: &self.pallet_name,
call_name: &self.call_name,
hash,
})
}
}
/// Construct a transaction at runtime; essentially an alias to [`DefaultPayload::new()`]
/// which provides a [`Composite`] value for the call data.
pub fn dynamic(
pallet_name: impl Into<String>,
call_name: impl Into<String>,
call_data: impl Into<Composite<()>>,
) -> DynamicPayload {
DefaultPayload::new(pallet_name, call_name, call_data.into())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Metadata;
use codec::Decode;
use scale_value::Composite;
fn test_metadata() -> Metadata {
let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
Metadata::decode(&mut &metadata_bytes[..]).expect("Valid metadata")
}
#[test]
fn encode_call_with_incompatible_types_returns_error() {
let metadata = test_metadata();
let incompatible_data = Composite::named([
("dest", scale_value::Value::bool(true)), // Boolean instead of MultiAddress
("value", scale_value::Value::string("not_a_number")), // String instead of u128
]);
let payload = DefaultPayload::new("Balances", "transfer_allow_death", incompatible_data);
let mut out = Vec::new();
let result = payload.encode_call_data_to(&metadata, &mut out);
assert!(
result.is_err(),
"Expected error when encoding with incompatible types"
);
}
#[test]
fn encode_call_with_valid_data_succeeds() {
let metadata = test_metadata();
// Create a valid payload to ensure our error handling doesn't break valid cases
// For MultiAddress, we'll use the Id variant with a 32-byte account
let valid_address =
scale_value::Value::unnamed_variant("Id", [scale_value::Value::from_bytes([0u8; 32])]);
let valid_data = Composite::named([
("dest", valid_address),
("value", scale_value::Value::u128(1000)),
]);
let payload = DefaultPayload::new("Balances", "transfer_allow_death", valid_data);
// This should succeed
let mut out = Vec::new();
let result = payload.encode_call_data_to(&metadata, &mut out);
assert!(
result.is_ok(),
"Expected success when encoding with valid data"
);
assert!(!out.is_empty(), "Expected encoded output to be non-empty");
}
}
-22
View File
@@ -1,22 +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 library to **sub**mit e**xt**rinsics to a
//! [substrate](https://github.com/paritytech/substrate) node via RPC.
use crate::Config;
/// Signing transactions requires a [`Signer`]. This is responsible for
/// providing the "from" account that the transaction is being signed by,
/// as well as actually signing a SCALE encoded payload.
pub trait Signer<T: Config> {
/// Return the "from" account ID.
fn account_id(&self) -> T::AccountId;
/// Takes a signer payload for an extrinsic, and returns a signature based on it.
///
/// Some signers may fail, for instance because the hardware on which the keys are located has
/// refused the operation.
fn sign(&self, signer_payload: &[u8]) -> T::Signature;
}
-152
View File
@@ -1,152 +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.
//! `AccountId20` is a representation of Ethereum address derived from hashing the public key.
use alloc::format;
use alloc::string::String;
use codec::{Decode, Encode};
use keccak_hash::keccak;
use serde::{Deserialize, Serialize};
use thiserror::Error as DeriveError;
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
scale_info::TypeInfo,
)]
/// Ethereum-compatible `AccountId`.
pub struct AccountId20(pub [u8; 20]);
impl AsRef<[u8]> for AccountId20 {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsRef<[u8; 20]> for AccountId20 {
fn as_ref(&self) -> &[u8; 20] {
&self.0
}
}
impl From<[u8; 20]> for AccountId20 {
fn from(x: [u8; 20]) -> Self {
AccountId20(x)
}
}
impl AccountId20 {
/// Convert to a public key hash
pub fn checksum(&self) -> String {
let hex_address = hex::encode(self.0);
let hash = keccak(hex_address.as_bytes());
let mut checksum_address = String::with_capacity(42);
checksum_address.push_str("0x");
for (i, ch) in hex_address.chars().enumerate() {
// Get the corresponding nibble from the hash
let nibble = (hash[i / 2] >> (if i % 2 == 0 { 4 } else { 0 })) & 0xf;
if nibble >= 8 {
checksum_address.push(ch.to_ascii_uppercase());
} else {
checksum_address.push(ch);
}
}
checksum_address
}
}
/// An error obtained from trying to interpret a hex encoded string into an AccountId20
#[derive(Clone, Copy, Eq, PartialEq, Debug, DeriveError)]
#[allow(missing_docs)]
pub enum FromChecksumError {
#[error("Length is bad")]
BadLength,
#[error("Invalid checksum")]
InvalidChecksum,
#[error("Invalid checksum prefix byte.")]
InvalidPrefix,
}
impl Serialize for AccountId20 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.checksum())
}
}
impl<'de> Deserialize<'de> for AccountId20 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<AccountId20>()
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
}
}
impl core::fmt::Display for AccountId20 {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.checksum())
}
}
impl core::str::FromStr for AccountId20 {
type Err = FromChecksumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 42 {
return Err(FromChecksumError::BadLength);
}
if !s.starts_with("0x") {
return Err(FromChecksumError::InvalidPrefix);
}
hex::decode(&s.as_bytes()[2..])
.map_err(|_| FromChecksumError::InvalidChecksum)?
.try_into()
.map(AccountId20)
.map_err(|_| FromChecksumError::BadLength)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn deserialisation() {
let key_hashes = vec![
"0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac",
"0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0",
"0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc",
"0x773539d4Ac0e786233D90A233654ccEE26a613D9",
"0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB",
"0xC0F0f4ab324C46e55D02D0033343B4Be8A55532d",
];
for key_hash in key_hashes {
let parsed: AccountId20 = key_hash.parse().expect("Failed to parse");
let encoded = parsed.checksum();
// `encoded` should be equal to the initial key_hash
assert_eq!(encoded, key_hash);
}
}
}
-266
View File
@@ -1,266 +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.
//! Generic `scale_bits` over `bitvec`-like `BitOrder` and `BitFormat` types.
use alloc::vec;
use alloc::vec::Vec;
use codec::{Compact, Input};
use core::marker::PhantomData;
use scale_bits::{
Bits,
scale::format::{Format, OrderFormat, StoreFormat},
};
use scale_decode::{IntoVisitor, TypeResolver};
/// Associates `bitvec::store::BitStore` trait with corresponding, type-erased `scale_bits::StoreFormat` enum.
///
/// Used to decode bit sequences by providing `scale_bits::StoreFormat` using
/// `bitvec`-like type type parameters.
pub trait BitStore {
/// Corresponding `scale_bits::StoreFormat` value.
const FORMAT: StoreFormat;
/// Number of bits that the backing store types holds.
const BITS: u32;
}
macro_rules! impl_store {
($ty:ident, $wrapped:ty) => {
impl BitStore for $wrapped {
const FORMAT: StoreFormat = StoreFormat::$ty;
const BITS: u32 = <$wrapped>::BITS;
}
};
}
impl_store!(U8, u8);
impl_store!(U16, u16);
impl_store!(U32, u32);
impl_store!(U64, u64);
/// Associates `bitvec::order::BitOrder` trait with corresponding, type-erased `scale_bits::OrderFormat` enum.
///
/// Used to decode bit sequences in runtime by providing `scale_bits::OrderFormat` using
/// `bitvec`-like type type parameters.
pub trait BitOrder {
/// Corresponding `scale_bits::OrderFormat` value.
const FORMAT: OrderFormat;
}
macro_rules! impl_order {
($ty:ident) => {
#[doc = concat!("Type-level value that corresponds to `scale_bits::OrderFormat::", stringify!($ty), "` at run-time")]
#[doc = concat!(" and `bitvec::order::BitOrder::", stringify!($ty), "` at the type level.")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum $ty {}
impl BitOrder for $ty {
const FORMAT: OrderFormat = OrderFormat::$ty;
}
};
}
impl_order!(Lsb0);
impl_order!(Msb0);
/// Constructs a run-time format parameters based on the corresponding type-level parameters.
fn bit_format<Store: BitStore, Order: BitOrder>() -> Format {
Format {
order: Order::FORMAT,
store: Store::FORMAT,
}
}
/// `scale_bits::Bits` generic over the bit store (`u8`/`u16`/`u32`/`u64`) and bit order (LSB, MSB)
/// used for SCALE encoding/decoding. Uses `scale_bits::Bits`-default `u8` and LSB format underneath.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedBits<Store, Order> {
bits: Bits,
_marker: PhantomData<(Store, Order)>,
}
impl<Store, Order> DecodedBits<Store, Order> {
/// Extracts the underlying `scale_bits::Bits` value.
pub fn into_bits(self) -> Bits {
self.bits
}
/// References the underlying `scale_bits::Bits` value.
pub fn as_bits(&self) -> &Bits {
&self.bits
}
}
impl<Store, Order> core::iter::FromIterator<bool> for DecodedBits<Store, Order> {
fn from_iter<T: IntoIterator<Item = bool>>(iter: T) -> Self {
DecodedBits {
bits: Bits::from_iter(iter),
_marker: PhantomData,
}
}
}
impl<Store: BitStore, Order: BitOrder> codec::Decode for DecodedBits<Store, Order> {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
/// Equivalent of `BitSlice::MAX_BITS` on 32bit machine.
const ARCH32BIT_BITSLICE_MAX_BITS: u32 = 0x1fff_ffff;
let Compact(bits) = <Compact<u32>>::decode(input)?;
// Otherwise it is impossible to store it on 32bit machine.
if bits > ARCH32BIT_BITSLICE_MAX_BITS {
return Err("Attempt to decode a BitVec with too many bits".into());
}
// NOTE: Replace with `bits.div_ceil(Store::BITS)` if `int_roundings` is stabilised
let elements = (bits / Store::BITS) + u32::from(bits % Store::BITS != 0);
let bytes_in_elem = Store::BITS.saturating_div(u8::BITS);
let bytes_needed = (elements * bytes_in_elem) as usize;
// NOTE: We could reduce allocations if it would be possible to directly
// decode from an `Input` type using a custom format (rather than default <u8, Lsb0>)
// for the `Bits` type.
let mut storage = codec::Encode::encode(&Compact(bits));
let prefix_len = storage.len();
storage.reserve_exact(bytes_needed);
storage.extend(vec![0; bytes_needed]);
input.read(&mut storage[prefix_len..])?;
let decoder = scale_bits::decode_using_format_from(&storage, bit_format::<Store, Order>())?;
let bits = decoder.collect::<Result<Vec<_>, _>>()?;
let bits = Bits::from_iter(bits);
Ok(DecodedBits {
bits,
_marker: PhantomData,
})
}
}
impl<Store: BitStore, Order: BitOrder> codec::Encode for DecodedBits<Store, Order> {
fn size_hint(&self) -> usize {
self.bits.size_hint()
}
fn encoded_size(&self) -> usize {
self.bits.encoded_size()
}
fn encode(&self) -> Vec<u8> {
scale_bits::encode_using_format(self.bits.iter(), bit_format::<Store, Order>())
}
}
#[doc(hidden)]
pub struct DecodedBitsVisitor<S, O, R: TypeResolver>(core::marker::PhantomData<(S, O, R)>);
impl<Store, Order, R: TypeResolver> scale_decode::Visitor for DecodedBitsVisitor<Store, Order, R> {
type Value<'scale, 'info> = DecodedBits<Store, Order>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
type_id: R::TypeId,
types: &'info R,
) -> scale_decode::visitor::DecodeAsTypeResult<
Self,
Result<Self::Value<'scale, 'info>, Self::Error>,
> {
let res =
scale_decode::visitor::decode_with_visitor(input, type_id, types, Bits::into_visitor())
.map(|bits| DecodedBits {
bits,
_marker: PhantomData,
});
scale_decode::visitor::DecodeAsTypeResult::Decoded(res)
}
}
impl<Store, Order> scale_decode::IntoVisitor for DecodedBits<Store, Order> {
type AnyVisitor<R: scale_decode::TypeResolver> = DecodedBitsVisitor<Store, Order, R>;
fn into_visitor<R: TypeResolver>() -> DecodedBitsVisitor<Store, Order, R> {
DecodedBitsVisitor(PhantomData)
}
}
impl<Store, Order> scale_encode::EncodeAsType for DecodedBits<Store, Order> {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.bits.encode_as_type_to(type_id, types, out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt::Debug;
use bitvec::vec::BitVec;
use codec::Decode as _;
// NOTE: We don't use `bitvec::order` types in our implementation, since we
// don't want to depend on `bitvec`. Rather than reimplementing the unsafe
// trait on our types here for testing purposes, we simply convert and
// delegate to `bitvec`'s own types.
trait ToBitVec {
type Order: bitvec::order::BitOrder;
}
impl ToBitVec for Lsb0 {
type Order = bitvec::order::Lsb0;
}
impl ToBitVec for Msb0 {
type Order = bitvec::order::Msb0;
}
fn scales_like_bitvec_and_roundtrips<
'a,
Store: BitStore + bitvec::store::BitStore + PartialEq,
Order: BitOrder + ToBitVec + Debug + PartialEq,
>(
input: impl IntoIterator<Item = &'a bool>,
) where
BitVec<Store, <Order as ToBitVec>::Order>: codec::Encode + codec::Decode,
{
let input: Vec<_> = input.into_iter().copied().collect();
let decoded_bits = DecodedBits::<Store, Order>::from_iter(input.clone());
let bitvec = BitVec::<Store, <Order as ToBitVec>::Order>::from_iter(input);
let decoded_bits_encoded = codec::Encode::encode(&decoded_bits);
let bitvec_encoded = codec::Encode::encode(&bitvec);
assert_eq!(decoded_bits_encoded, bitvec_encoded);
let decoded_bits_decoded =
DecodedBits::<Store, Order>::decode(&mut &decoded_bits_encoded[..])
.expect("SCALE-encoding DecodedBits to roundtrip");
let bitvec_decoded =
BitVec::<Store, <Order as ToBitVec>::Order>::decode(&mut &bitvec_encoded[..])
.expect("SCALE-encoding BitVec to roundtrip");
assert_eq!(decoded_bits, decoded_bits_decoded);
assert_eq!(bitvec, bitvec_decoded);
}
#[test]
fn decoded_bitvec_scales_and_roundtrips() {
let test_cases = [
vec![],
vec![true],
vec![false],
vec![true, false, true],
vec![true, false, true, false, false, false, false, false, true],
[vec![true; 5], vec![false; 5], vec![true; 1], vec![false; 3]].concat(),
[vec![true; 9], vec![false; 9], vec![true; 9], vec![false; 9]].concat(),
];
for test_case in &test_cases {
scales_like_bitvec_and_roundtrips::<u8, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u16, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u32, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u64, Lsb0>(test_case);
scales_like_bitvec_and_roundtrips::<u8, Msb0>(test_case);
scales_like_bitvec_and_roundtrips::<u16, Msb0>(test_case);
scales_like_bitvec_and_roundtrips::<u32, Msb0>(test_case);
scales_like_bitvec_and_roundtrips::<u64, Msb0>(test_case);
}
}
}
-234
View File
@@ -1,234 +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 alloc::{format, vec::Vec};
use codec::{Decode, Encode};
use scale_decode::{
IntoVisitor, TypeResolver, Visitor,
ext::scale_type_resolver,
visitor::{TypeIdFor, types::Composite, types::Variant},
};
use scale_encode::EncodeAsType;
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
/// An era to describe the longevity of a transaction.
#[derive(
PartialEq,
Default,
Eq,
Clone,
Copy,
Debug,
serde::Serialize,
serde::Deserialize,
scale_info::TypeInfo,
)]
pub enum Era {
/// The transaction is valid forever. The genesis hash must be present in the signed content.
#[default]
Immortal,
/// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
///
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
/// of `system` module.
Mortal {
/// The number of blocks that the tx will be valid for after the checkpoint block
/// hash found in the signer payload.
period: u64,
/// The phase in the period that this transaction's lifetime begins (and, importantly,
/// implies which block hash is included in the signature material). If the `period` is
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
/// `period` is.
phase: u64,
},
}
// E.g. with period == 4:
// 0 10 20 30 40
// 0123456789012345678901234567890123456789012
// |...|
// authored -/ \- expiry
// phase = 1
// n = Q(current - phase, period) + phase
impl Era {
/// Create a new era based on a period (which should be a power of two between 4 and 65536
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
/// after the start).
///
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
/// prunes old blocks and renders transactions immediately invalid.
pub fn mortal(period: u64, current: u64) -> Self {
let period = period
.checked_next_power_of_two()
.unwrap_or(1 << 16)
.clamp(4, 1 << 16);
let phase = current % period;
let quantize_factor = (period >> 12).max(1);
let quantized_phase = phase / quantize_factor * quantize_factor;
Self::Mortal {
period,
phase: quantized_phase,
}
}
}
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
// it's really the most important bit here.
impl codec::Encode for Era {
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
match self {
Self::Immortal => output.push_byte(0),
Self::Mortal { period, phase } => {
let quantize_factor = (*period >> 12).max(1);
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
| ((phase / quantize_factor) << 4) as u16;
encoded.encode_to(output);
}
}
}
}
impl codec::Decode for Era {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let first = input.read_byte()?;
if first == 0 {
Ok(Self::Immortal)
} else {
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
let period = 2 << (encoded % (1 << 4));
let quantize_factor = (period >> 12).max(1);
let phase = (encoded >> 4) * quantize_factor;
if period >= 4 && phase < period {
Ok(Self::Mortal { period, phase })
} else {
Err("Invalid period and phase".into())
}
}
}
}
/// Define manually how to encode an Era given some type information. Here we
/// basically check that the type we're targeting is called "Era" and then codec::Encode.
impl EncodeAsType for Era {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
// Visit the type to check that it is an Era. This is only a rough check.
let visitor = scale_type_resolver::visitor::new((), |_, _| false)
.visit_variant(|_, path, _variants| path.last() == Some("Era"));
let is_era = types
.resolve_type(type_id.clone(), visitor)
.unwrap_or_default();
if !is_era {
return Err(scale_encode::Error::custom_string(format!(
"Type {type_id:?} is not a valid Era type; expecting either Immortal or MortalX variant"
)));
}
// if the type looks valid then just scale encode our Era.
self.encode_to(out);
Ok(())
}
}
/// Define manually how to decode an Era given some type information. Here we check that the
/// variant we're decoding is one of the expected Era variants, and that the field is correct if so,
/// ensuring that this will fail if trying to decode something that isn't an Era.
pub struct EraVisitor<R>(core::marker::PhantomData<R>);
impl IntoVisitor for Era {
type AnyVisitor<R: TypeResolver> = EraVisitor<R>;
fn into_visitor<R: TypeResolver>() -> Self::AnyVisitor<R> {
EraVisitor(core::marker::PhantomData)
}
}
impl<R: TypeResolver> Visitor for EraVisitor<R> {
type Value<'scale, 'resolver> = Era;
type Error = scale_decode::Error;
type TypeResolver = R;
// Unwrap any newtype wrappers around the era, eg the CheckMortality extension (which actually
// has 2 fields, but scale_info seems to automatically ignore the PhantomData field). This
// allows us to decode directly from CheckMortality into Era.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
if value.remaining() != 1 {
return Err(scale_decode::Error::custom_string(format!(
"Expected any wrapper around Era to have exactly one field, but got {} fields",
value.remaining()
)));
}
value
.decode_item(self)
.expect("1 field expected; checked above.")
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let variant = value.name();
// If the variant is immortal, we know the outcome.
if variant == "Immortal" {
return Ok(Era::Immortal);
}
// Otherwise, we expect a variant Mortal1..Mortal255 where the number
// here is the first byte, and the second byte is conceptually a field of this variant.
// This weird encoding is because the Era is compressed to just 1 byte if immortal and
// just 2 bytes if mortal.
//
// Note: We _could_ just assume we'll have 2 bytes to work with and decode the era directly,
// but checking the variant names ensures that the thing we think is an Era actually _is_
// one, based on the type info for it.
let first_byte = variant
.strip_prefix("Mortal")
.and_then(|s| s.parse::<u8>().ok())
.ok_or_else(|| {
scale_decode::Error::custom_string(format!(
"Expected MortalX variant, but got {variant}"
))
})?;
// We need 1 field in the MortalN variant containing the second byte.
let mortal_fields = value.fields();
if mortal_fields.remaining() != 1 {
return Err(scale_decode::Error::custom_string(format!(
"Expected Mortal{} to have one u8 field, but got {} fields",
first_byte,
mortal_fields.remaining()
)));
}
let second_byte = mortal_fields
.decode_item(u8::into_visitor())
.expect("At least one field should exist; checked above.")
.map_err(|e| {
scale_decode::Error::custom_string(format!(
"Expected mortal variant field to be u8, but: {e}"
))
})?;
// Now that we have both bytes we can decode them into the era using
// the same logic as the codec::Decode impl does.
Era::decode(&mut &[first_byte, second_byte][..]).map_err(|e| {
scale_decode::Error::custom_string(format!(
"Failed to codec::Decode Era from Mortal bytes: {e}"
))
})
}
}
-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.
//! Miscellaneous utility helpers.
mod account_id;
mod account_id20;
pub mod bits;
mod era;
mod multi_address;
mod multi_signature;
mod static_type;
mod unchecked_extrinsic;
mod wrapper_opaque;
mod yesnomaybe;
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use codec::{Compact, Decode, Encode};
use derive_where::derive_where;
pub use account_id::AccountId32;
pub use account_id20::AccountId20;
pub use era::Era;
pub use multi_address::MultiAddress;
pub use multi_signature::MultiSignature;
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
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Encoded(pub Vec<u8>);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec<u8> {
self.0.to_owned()
}
}
/// Decodes a compact encoded value from the beginning of the provided bytes,
/// returning the value and any remaining bytes.
pub fn strip_compact_prefix(bytes: &[u8]) -> Result<(u64, &[u8]), codec::Error> {
let cursor = &mut &*bytes;
let val = <Compact<u64>>::decode(cursor)?;
Ok((val.0, *cursor))
}
/// A version of [`core::marker::PhantomData`] that is also Send and Sync (which is fine
/// because regardless of the generic param, it is always possible to Send + Sync this
/// 0 size type).
#[derive(Encode, Decode, scale_info::TypeInfo)]
#[derive_where(Clone, PartialEq, Debug, Eq, Default, Hash)]
#[scale_info(skip_type_params(T))]
#[doc(hidden)]
pub struct PhantomDataSendSync<T>(core::marker::PhantomData<T>);
impl<T> PhantomDataSendSync<T> {
pub fn new() -> Self {
Self(core::marker::PhantomData)
}
}
unsafe impl<T> Send for PhantomDataSendSync<T> {}
unsafe impl<T> Sync for PhantomDataSendSync<T> {}
/// This represents a key-value collection and is SCALE compatible
/// with collections like BTreeMap. This has the same type params
/// as `BTreeMap` which allows us to easily swap the two during codegen.
pub type KeyedVec<K, V> = Vec<(K, V)>;
/// 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()))
}
-44
View File
@@ -1,44 +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.
//! The "default" Substrate/Polkadot Address type. This is used in codegen, as well as signing related bits.
//! This doesn't contain much functionality itself, but is easy to convert to/from an `sp_runtime::MultiAddress`
//! for instance, to gain functionality without forcing a dependency on Substrate crates here.
use alloc::vec::Vec;
use codec::{Decode, Encode};
/// A multi-format address wrapper for on-chain accounts. This is a simplified version of Substrate's
/// `sp_runtime::MultiAddress`.
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
Debug,
scale_encode::EncodeAsType,
scale_decode::DecodeAsType,
scale_info::TypeInfo,
)]
pub enum MultiAddress<AccountId, AccountIndex> {
/// It's an account ID (pubkey).
Id(AccountId),
/// It's an account index.
Index(#[codec(compact)] AccountIndex),
/// It's some arbitrary raw bytes.
Raw(Vec<u8>),
/// It's a 32 byte representation.
Address32([u8; 32]),
/// Its a 20 byte representation.
Address20([u8; 20]),
}
impl<AccountId, AccountIndex> From<AccountId> for MultiAddress<AccountId, AccountIndex> {
fn from(a: AccountId) -> Self {
Self::Id(a)
}
}
-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 codec::{Decode, Encode};
use scale_decode::{IntoVisitor, TypeResolver, Visitor, visitor::DecodeAsTypeResult};
use scale_encode::EncodeAsType;
use alloc::vec::Vec;
/// If the type inside this implements [`Encode`], this will implement [`scale_encode::EncodeAsType`].
/// If the type inside this implements [`Decode`], this will implement [`scale_decode::DecodeAsType`].
///
/// In either direction, we ignore any type information and just attempt to encode/decode statically
/// via the [`Encode`] and [`Decode`] implementations. This can be useful as an adapter for types which
/// do not implement [`scale_encode::EncodeAsType`] and [`scale_decode::DecodeAsType`] themselves, but
/// it's best to avoid using it where possible as it will not take into account any type information,
/// and is thus more likely to encode or decode incorrectly.
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct Static<T>(pub T);
impl<T: Encode> EncodeAsType for Static<T> {
fn encode_as_type_to<R: TypeResolver>(
&self,
_type_id: R::TypeId,
_types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.0.encode_to(out);
Ok(())
}
}
pub struct StaticDecodeAsTypeVisitor<T, R>(core::marker::PhantomData<(T, R)>);
impl<T: Decode, R: TypeResolver> Visitor for StaticDecodeAsTypeVisitor<T, R> {
type Value<'scale, 'info> = Static<T>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
_type_id: R::TypeId,
_types: &'info R,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
use scale_decode::{Error, visitor::DecodeError};
let decoded = T::decode(input)
.map(Static)
.map_err(|e| Error::new(DecodeError::CodecError(e).into()));
DecodeAsTypeResult::Decoded(decoded)
}
}
impl<T: Decode> IntoVisitor for Static<T> {
type AnyVisitor<R: TypeResolver> = StaticDecodeAsTypeVisitor<T, R>;
fn into_visitor<R: TypeResolver>() -> StaticDecodeAsTypeVisitor<T, R> {
StaticDecodeAsTypeVisitor(core::marker::PhantomData)
}
}
// Make it easy to convert types into Static where required.
impl<T> From<T> for Static<T> {
fn from(value: T) -> Self {
Static(value)
}
}
// Static<T> is just a marker type and should be as transparent as possible:
impl<T> core::ops::Deref for Static<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for Static<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
-142
View File
@@ -1,142 +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.
//! The "default" Substrate/Polkadot UncheckedExtrinsic.
//! This is used in codegen for runtime API calls.
//!
//! The inner bytes represent the encoded extrinsic expected by the
//! runtime APIs. Deriving `EncodeAsType` would lead to the inner
//! bytes to be re-encoded (length prefixed).
use core::marker::PhantomData;
use codec::{Decode, Encode};
use scale_decode::{DecodeAsType, IntoVisitor, TypeResolver, Visitor, visitor::DecodeAsTypeResult};
use super::{Encoded, Static};
use alloc::vec::Vec;
/// The unchecked extrinsic from substrate.
#[derive(Clone, Debug, Eq, PartialEq, Encode)]
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>(
Static<Encoded>,
#[codec(skip)] PhantomData<(Address, Call, Signature, Extra)>,
);
impl<Address, Call, Signature, Extra> UncheckedExtrinsic<Address, Call, Signature, Extra> {
/// Construct a new [`UncheckedExtrinsic`].
pub fn new(bytes: Vec<u8>) -> Self {
Self(Static(Encoded(bytes)), PhantomData)
}
/// Get the bytes of the encoded extrinsic.
pub fn bytes(&self) -> &[u8] {
self.0.0.0.as_slice()
}
}
impl<Address, Call, Signature, Extra> Decode
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
// The bytes for an UncheckedExtrinsic are first a compact
// encoded length, and then the bytes following. This is the
// same encoding as a Vec, so easiest ATM is just to decode
// into that, and then encode the vec bytes to get our extrinsic
// bytes, which we save into an `Encoded` to preserve as-is.
let xt_vec: Vec<u8> = Decode::decode(input)?;
Ok(UncheckedExtrinsic::new(xt_vec))
}
}
impl<Address, Call, Signature, Extra> scale_encode::EncodeAsType
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
self.0.encode_as_type_to(type_id, types, out)
}
}
impl<Address, Call, Signature, Extra> From<Vec<u8>>
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
fn from(bytes: Vec<u8>) -> Self {
UncheckedExtrinsic::new(bytes)
}
}
impl<Address, Call, Signature, Extra> From<UncheckedExtrinsic<Address, Call, Signature, Extra>>
for Vec<u8>
{
fn from(bytes: UncheckedExtrinsic<Address, Call, Signature, Extra>) -> Self {
bytes.0.0.0
}
}
pub struct UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R: TypeResolver>(
PhantomData<(Address, Call, Signature, Extra, R)>,
);
impl<Address, Call, Signature, Extra, R: TypeResolver> Visitor
for UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R>
{
type Value<'scale, 'info> = UncheckedExtrinsic<Address, Call, Signature, Extra>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
type_id: R::TypeId,
types: &'info R,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
DecodeAsTypeResult::Decoded(Self::Value::decode_as_type(input, type_id, types))
}
}
impl<Address, Call, Signature, Extra> IntoVisitor
for UncheckedExtrinsic<Address, Call, Signature, Extra>
{
type AnyVisitor<R: TypeResolver> =
UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R>;
fn into_visitor<R: TypeResolver>()
-> UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra, R> {
UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use alloc::vec;
#[test]
fn unchecked_extrinsic_encoding() {
// A tx is basically some bytes with a compact length prefix; ie an encoded vec:
let tx_bytes = vec![1u8, 2, 3].encode();
let unchecked_extrinsic = UncheckedExtrinsic::<(), (), (), ()>::new(tx_bytes.clone());
let encoded_tx_bytes = unchecked_extrinsic.encode();
// The encoded representation must not alter the provided bytes.
assert_eq!(tx_bytes, encoded_tx_bytes);
// However, for decoding we expect to be able to read the extrinsic from the wire
// which would be length prefixed.
let decoded_tx = UncheckedExtrinsic::<(), (), (), ()>::decode(&mut &tx_bytes[..]).unwrap();
let decoded_tx_bytes = decoded_tx.bytes();
let encoded_tx_bytes = decoded_tx.encode();
assert_eq!(decoded_tx_bytes, encoded_tx_bytes);
// Ensure we can decode the tx and fetch only the tx bytes.
assert_eq!(vec![1, 2, 3], encoded_tx_bytes);
}
}
-242
View File
@@ -1,242 +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::PhantomDataSendSync;
use codec::{Compact, Decode, DecodeAll, Encode};
use derive_where::derive_where;
use scale_decode::{IntoVisitor, TypeResolver, Visitor, ext::scale_type_resolver::visitor};
use scale_encode::EncodeAsType;
use alloc::format;
use alloc::vec::Vec;
/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec<u8>`.
/// [`WrapperKeepOpaque`] stores the type only in its opaque format, aka as a `Vec<u8>`. To
/// access the real type `T` [`Self::try_decode`] needs to be used.
// Dev notes:
//
// - This is adapted from [here](https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs).
// - The encoded bytes will be a compact encoded length followed by that number of bytes.
// - However, the TypeInfo describes the type as a composite with first a compact encoded length and next the type itself.
// [`Encode`] and [`Decode`] impls will "just work" to take this into a `Vec<u8>`, but we need a custom [`EncodeAsType`]
// and [`Visitor`] implementation to encode and decode based on TypeInfo.
#[derive(Encode, Decode)]
#[derive_where(Debug, Clone, PartialEq, Eq, Default, Hash)]
pub struct WrapperKeepOpaque<T> {
data: Vec<u8>,
_phantom: PhantomDataSendSync<T>,
}
impl<T> WrapperKeepOpaque<T> {
/// Try to decode the wrapped type from the inner `data`.
///
/// Returns `None` if the decoding failed.
pub fn try_decode(&self) -> Option<T>
where
T: Decode,
{
T::decode_all(&mut &self.data[..]).ok()
}
/// Returns the length of the encoded `T`.
pub fn encoded_len(&self) -> usize {
self.data.len()
}
/// Returns the encoded data.
pub fn encoded(&self) -> &[u8] {
&self.data
}
/// Create from the given encoded `data`.
pub fn from_encoded(data: Vec<u8>) -> Self {
Self {
data,
_phantom: PhantomDataSendSync::new(),
}
}
/// Create from some raw value by encoding it.
pub fn from_value(value: T) -> Self
where
T: Encode,
{
Self {
data: value.encode(),
_phantom: PhantomDataSendSync::new(),
}
}
}
impl<T> EncodeAsType for WrapperKeepOpaque<T> {
fn encode_as_type_to<R: TypeResolver>(
&self,
type_id: R::TypeId,
types: &R,
out: &mut Vec<u8>,
) -> Result<(), scale_encode::Error> {
use scale_encode::error::{Error, ErrorKind, Kind};
let ctx = (type_id.clone(), out);
let visitor = visitor::new(ctx, |(type_id, _out), _| {
// Check that the target shape lines up: any other shape but composite is wrong.
Err(Error::new(ErrorKind::WrongShape {
actual: Kind::Struct,
expected_id: format!("{type_id:?}"),
}))
})
.visit_composite(|(_type_id, out), _path, _fields| {
self.data.encode_to(out);
Ok(())
});
types
.resolve_type(type_id.clone(), visitor)
.map_err(|_| Error::new(ErrorKind::TypeNotFound(format!("{type_id:?}"))))?
}
}
pub struct WrapperKeepOpaqueVisitor<T, R>(core::marker::PhantomData<(T, R)>);
impl<T, R: TypeResolver> Visitor for WrapperKeepOpaqueVisitor<T, R> {
type Value<'scale, 'info> = WrapperKeepOpaque<T>;
type Error = scale_decode::Error;
type TypeResolver = R;
fn visit_composite<'scale, 'info>(
self,
value: &mut scale_decode::visitor::types::Composite<'scale, 'info, R>,
_type_id: R::TypeId,
) -> Result<Self::Value<'scale, 'info>, Self::Error> {
use scale_decode::error::{Error, ErrorKind};
use scale_decode::visitor::DecodeError;
if value.name() != Some("WrapperKeepOpaque") {
return Err(Error::new(ErrorKind::VisitorDecodeError(
DecodeError::TypeResolvingError(format!(
"Expected a type named 'WrapperKeepOpaque', got: {:?}",
value.name()
)),
)));
}
if value.remaining() != 2 {
return Err(Error::new(ErrorKind::WrongLength {
actual_len: value.remaining(),
expected_len: 2,
}));
}
// The field to decode is a compact len followed by bytes. Decode the length, then grab the bytes.
let Compact(len) = value
.decode_item(Compact::<u32>::into_visitor())
.expect("length checked")?;
let field = value.next().expect("length checked")?;
// Sanity check that the compact length we decoded lines up with the number of bytes encoded in the next field.
if field.bytes().len() != len as usize {
return Err(Error::custom_str(
"WrapperTypeKeepOpaque compact encoded length doesn't line up with encoded byte len",
));
}
Ok(WrapperKeepOpaque {
data: field.bytes().to_vec(),
_phantom: PhantomDataSendSync::new(),
})
}
}
impl<T> IntoVisitor for WrapperKeepOpaque<T> {
type AnyVisitor<R: TypeResolver> = WrapperKeepOpaqueVisitor<T, R>;
fn into_visitor<R: TypeResolver>() -> WrapperKeepOpaqueVisitor<T, R> {
WrapperKeepOpaqueVisitor(core::marker::PhantomData)
}
}
#[cfg(test)]
mod test {
use scale_decode::DecodeAsType;
use alloc::vec;
use super::*;
// Copied from https://github.com/paritytech/substrate/blob/master/frame/support/src/traits/misc.rs
// and used for tests to check that we can work with the expected TypeInfo without needing to import
// the frame_support crate, which has quite a lot of dependencies.
impl<T: scale_info::TypeInfo + 'static> scale_info::TypeInfo for WrapperKeepOpaque<T> {
type Identity = Self;
fn type_info() -> scale_info::Type {
use scale_info::{Path, Type, TypeParameter, build::Fields, meta_type};
Type::builder()
.path(Path::new("WrapperKeepOpaque", module_path!()))
.type_params(vec![TypeParameter::new("T", Some(meta_type::<T>()))])
.composite(
Fields::unnamed()
.field(|f| f.compact::<u32>())
.field(|f| f.ty::<T>().type_name("T")),
)
}
}
/// Given a type definition, return type ID and registry representing it.
fn make_type<T: scale_info::TypeInfo + 'static>() -> (u32, scale_info::PortableRegistry) {
let m = scale_info::MetaType::new::<T>();
let mut types = scale_info::Registry::new();
let id = types.register_type(&m);
let portable_registry: scale_info::PortableRegistry = types.into();
(id.id, portable_registry)
}
fn roundtrips_like_scale_codec<T>(t: T)
where
T: EncodeAsType
+ DecodeAsType
+ Encode
+ Decode
+ PartialEq
+ core::fmt::Debug
+ scale_info::TypeInfo
+ 'static,
{
let (type_id, types) = make_type::<T>();
let scale_codec_encoded = t.encode();
let encode_as_type_encoded = t.encode_as_type(type_id, &types).unwrap();
assert_eq!(
scale_codec_encoded, encode_as_type_encoded,
"encoded bytes should match"
);
let decode_as_type_bytes = &mut &*scale_codec_encoded;
let decoded_as_type = T::decode_as_type(decode_as_type_bytes, type_id, &types)
.expect("decode-as-type decodes");
let decode_scale_codec_bytes = &mut &*scale_codec_encoded;
let decoded_scale_codec = T::decode(decode_scale_codec_bytes).expect("scale-codec decodes");
assert!(
decode_as_type_bytes.is_empty(),
"no bytes should remain in decode-as-type impl"
);
assert!(
decode_scale_codec_bytes.is_empty(),
"no bytes should remain in codec-decode impl"
);
assert_eq!(
decoded_as_type, decoded_scale_codec,
"decoded values should match"
);
}
#[test]
fn wrapper_keep_opaque_roundtrips_ok() {
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(123u64));
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(true));
roundtrips_like_scale_codec(WrapperKeepOpaque::from_value(vec![1u8, 2, 3, 4]));
}
}
-83
View File
@@ -1,83 +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.
//! Encode View Function payloads, decode the associated values returned from them, and validate
//! static View Function payloads.
pub mod payload;
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<(), ViewFunctionError> {
let Some(hash) = payload.validation_hash() else {
return Ok(());
};
let pallet_name = payload.pallet_name();
let function_name = payload.function_name();
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
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>, 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)?;
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,
metadata: &Metadata,
) -> 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(value)
}
-165
View File
@@ -1,165 +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.
//! This module contains the trait and types used to represent
//! View Function calls that can be made.
use alloc::borrow::Cow;
use alloc::string::String;
use core::marker::PhantomData;
use derive_where::derive_where;
use frame_decode::view_functions::IntoEncodableValues;
use scale_decode::DecodeAsType;
/// This represents a View Function payload that can call into the runtime of node.
///
/// # Components
///
/// - associated return type
///
/// Resulting bytes of the call are interpreted into this type.
///
/// - query ID
///
/// The ID used to identify in the runtime which view function to call.
///
/// - encoded arguments
///
/// 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.
type ReturnType: DecodeAsType;
/// The View Function pallet name.
fn pallet_name(&self) -> &str;
/// The View Function function name.
fn function_name(&self) -> &str;
/// The arguments.
fn args(&self) -> &Self::ArgsType;
/// Returns the statically generated validation hash.
fn validation_hash(&self) -> Option<[u8; 32]> {
None
}
}
// 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 `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; 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<ReturnType>,
}
/// A dynamic View Function payload.
pub type DynamicPayload<ArgsType, ReturnType> = StaticPayload<ArgsType, ReturnType>;
impl<ArgsType: IntoEncodableValues, ReturnType: DecodeAsType> Payload
for StaticPayload<ArgsType, ReturnType>
{
type ArgsType = ArgsType;
type ReturnType = ReturnType;
fn pallet_name(&self) -> &str {
&self.pallet_name
}
fn function_name(&self) -> &str {
&self.function_name
}
fn args(&self) -> &Self::ArgsType {
&self.args
}
fn validation_hash(&self) -> Option<[u8; 32]> {
self.validation_hash
}
}
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 [`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(
pallet_name: &'static str,
function_name: &'static str,
args: ArgsType,
hash: [u8; 32],
) -> 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,
}
}
/// Do not validate this call prior to submitting it.
pub fn unvalidated(self) -> Self {
Self {
validation_hash: None,
..self
}
}
}
/// Create a new [`DynamicPayload`] to call a View Function.
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)
}
-37
View File
@@ -1,37 +0,0 @@
# subxt-historic changelog
This is separate from the Subxt changelog as subxt-historic is currently releasaed separately.
Eventually this project will merge with Subxt and no longer exist, but until then it's being maintained and updated where needed.
## 0.0.8 (2025-12-04)
Expose `ClientAtBlock::resolver()`. This hands back a type resolver which is capable of resolving type IDs given by the `.visit()` methods on extrinsic fields and storage values. The extrinsics example has been modified to show how this can be used.
## 0.0.7 (2025-12-03)
Expose `OfflineClientAtBlock`, `OfflineClientAtBlockT`, `OnlinelientAtBlock`, `OnlineClientAtBlockT`.
This is so that you can pass the `ClientAtBlock` into functions like so:
```rust
use subxt_historic::config::Config;
use subxt_historic::client::{ ClientAtBlock, OnlineClientAtBlock, OnlineClientAtBlockT };
fn accepts_client_at_block_concrete<T: Config>(client: &ClientAtBlock<OnlineClientAtBlock<'_, T>, T>) {
// ...
}
fn accepts_client_at_block_generic<'conf, T: Config + 'conf, C: OnlineClientAtBlockT<'conf, T>>(client: &ClientAtBlock<C, T>) {
// ...
}
```
## 0.0.6 (2025-12-01)
- Add `.metadata()` on `ClientAtBlock` to expose the current metadata at some block.
## 0.0.5 (2025-11-21)
- Rename some fields for consistency.
- Update versions of underlying libraries being used.
- Add `.visit()` methods to extrinsic fields and storage values, and examples of using this to our examples.
-63
View File
@@ -1,63 +0,0 @@
[package]
name = "subxt-historic"
version = "0.0.8"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
license.workspace = true
readme = "README.md"
repository.workspace = true
documentation.workspace = true
homepage.workspace = true
description = "Download non head-of-chain blocks and state from Substrate based nodes"
keywords = ["parity", "substrate", "blockchain"]
[lints]
workspace = true
[features]
default = ["jsonrpsee", "native"]
# Enable this for native (ie non web/wasm builds).
# Exactly 1 of "web" and "native" is expected.
native = [
"subxt-rpcs/native",
]
# Enable this for web/wasm builds.
# Exactly 1 of "web" and "native" is expected.
web = [
"subxt-rpcs/web",
]
# Enable this to use the reconnecting rpc client
reconnecting-rpc-client = ["subxt-rpcs/reconnecting-rpc-client"]
# Enable this to use jsonrpsee, which enables the jsonrpsee RPC client, and
# a couple of util functions which rely on jsonrpsee.
jsonrpsee = [
"subxt-rpcs/jsonrpsee",
]
[dependencies]
subxt-rpcs = { workspace = true }
frame-decode = { workspace = true, features = ["legacy", "legacy-types"] }
frame-metadata = { workspace = true, features = ["std", "legacy"] }
scale-type-resolver = { workspace = true, features = ["scale-info"] }
codec = { workspace = true }
primitive-types = { workspace = true }
scale-info = { workspace = true }
scale-info-legacy = { workspace = true }
scale-decode = { workspace = true }
thiserror = { workspace = true }
sp-crypto-hashing = { workspace = true }
url = { workspace = true }
futures = { workspace = true }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
scale-value = { workspace = true }
scale-decode = { workspace = true, features = ["derive"] }
hex = { workspace = true }
-16
View File
@@ -1,16 +0,0 @@
# subxt-historic
**This crate is a work in progress and currently is released only as a preview.**
While `subxt` is a library for working at the head of a chain (submitting transactions and obtaining the current state), `subxt-historic` is a library for decoding blocks and state that are anywhere in a chain. To broadly summarize the differences:
| Feature | subxt | subxt-historic |
|-----------------------------------------|------------------------------|-------------------------------|
| Block access | Head of chain | Any block in chain |
| Connection to chain | Light client or RPC node | Archive RPC nodes only |
| Transaction submission | Yes | No |
| Metadata compatibility | V14 and newer | Any version |
# Examples
See the [examples](https://github.com/paritytech/subxt/tree/master/historic/examples) folder for examples of how to use `subxt-historic`.
-495
View File
@@ -1,495 +0,0 @@
#![allow(missing_docs)]
use subxt_historic::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
// Configuration for the Polkadot relay chain.
let config = PolkadotConfig::new();
// Create an online client for the Polkadot relay chain, pointed at a Polkadot archive node.
let client = OnlineClient::from_url(config, "wss://rpc.polkadot.io").await?;
// Iterate through some randomly selected old blocks to show how to fetch and decode extrinsics.
for block_number in 1234567.. {
println!("=== Block {block_number} ===");
// Point the client at a specific block number. By default this will download and cache
// metadata for the required spec version (so it's cheaper to instantiate again), if it
// hasn't already, and borrow the relevant legacy types from the client.
let client_at_block = client.at(block_number).await?;
// Fetch the extrinsics at that block.
let extrinsics = client_at_block.extrinsics().fetch().await?;
// Now, we have various operations to work with them. Here we print out various details
// about each extrinsic.
for extrinsic in extrinsics.iter() {
println!(
"{}.{}",
extrinsic.call().pallet_name(),
extrinsic.call().name()
);
if let Some(signature) = extrinsic.signature_bytes() {
println!(" Signature: 0x{}", hex::encode(signature));
}
println!(" Call Data:");
// We can decode each of the fields (in this example we decode everything into a
// scale_value::Value type, which can represent any SCALE encoded data, but if you
// have an idea of the type then you can try to decode into that type instead):
for field in extrinsic.call().fields().iter() {
// We can visit fields, which gives us the ability to inspect and decode information
// from them selectively, returning whatever we like from it. Here we demo our
// type name visitor which is defined below:
let tn = field
.visit(type_name::GetTypeName::new())?
.unwrap_or_default();
// When visiting fields we can also decode into a custom shape like so:
let _custom_value =
field.visit(value::GetValue::new(&client_at_block.resolver()))?;
// We can also obtain and decode things without the complexity of the above:
println!(
" {}: {} {}",
field.name(),
field.decode_as::<scale_value::Value>().unwrap(),
if tn.is_empty() {
String::new()
} else {
format!("(type name: {tn})")
},
);
}
// Or, all of them at once:
println!(
" All: {}",
extrinsic
.call()
.fields()
.decode_as::<scale_value::Composite<_>>()
.unwrap()
);
// We can also look at things like the transaction extensions:
if let Some(extensions) = extrinsic.transaction_extensions() {
println!(" Transaction Extensions:");
// We can decode each of them:
for extension in extensions.iter() {
println!(
" {}: {}",
extension.name(),
extension.decode_as::<scale_value::Value>().unwrap()
);
}
// Or all of them at once:
println!(
" All: {}",
extensions.decode_as::<scale_value::Composite<_>>().unwrap()
);
}
}
}
Ok(())
}
/// This module defines an example visitor which retrieves the name of a type.
/// This is a more advanced use case and can typically be avoided.
mod type_name {
use scale_decode::{
Visitor,
visitor::types::{Composite, Sequence, Variant},
visitor::{TypeIdFor, Unexpected},
};
use scale_type_resolver::TypeResolver;
/// This is a visitor which obtains type names.
pub struct GetTypeName<R> {
marker: core::marker::PhantomData<R>,
}
impl<R> GetTypeName<R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetTypeName {
marker: core::marker::PhantomData,
}
}
}
impl<R: TypeResolver> Visitor for GetTypeName<R> {
type Value<'scale, 'resolver> = Option<&'resolver str>;
type Error = scale_decode::Error;
type TypeResolver = R;
// Look at the path of types that have paths and return the ident from that.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_sequence<'scale, 'resolver>(
self,
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
// Else, we return nothing as we can't find a name for the type.
fn visit_unexpected<'scale, 'resolver>(
self,
_unexpected: Unexpected,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(None)
}
}
}
/// This visitor demonstrates how to decode and return a custom Value shape
mod value {
use scale_decode::{
Visitor,
visitor::TypeIdFor,
visitor::types::{Array, BitSequence, Composite, Sequence, Str, Tuple, Variant},
};
use scale_type_resolver::TypeResolver;
use std::collections::HashMap;
/// A value type we're decoding into.
#[allow(dead_code)]
pub enum Value {
Number(f64),
BigNumber(String),
Bool(bool),
Char(char),
Array(Vec<Value>),
String(String),
Address(Vec<u8>),
I256([u8; 32]),
U256([u8; 32]),
Struct(HashMap<String, Value>),
VariantWithoutData(String),
VariantWithData(String, VariantFields),
}
pub enum VariantFields {
Unnamed(Vec<Value>),
Named(HashMap<String, Value>),
}
/// An error we can encounter trying to decode things into a [`Value`]
#[derive(Debug, thiserror::Error)]
pub enum ValueError {
#[error("Decode error: {0}")]
Decode(#[from] scale_decode::visitor::DecodeError),
#[error("Cannot decode bit sequence: {0}")]
CannotDecodeBitSequence(codec::Error),
#[error("Cannot resolve variant type information: {0}")]
CannotResolveVariantType(String),
}
/// This is a visitor which obtains type names.
pub struct GetValue<'r, R> {
resolver: &'r R,
}
impl<'r, R> GetValue<'r, R> {
/// Construct our TypeName visitor.
pub fn new(resolver: &'r R) -> Self {
GetValue { resolver }
}
}
impl<'r, R: TypeResolver> Visitor for GetValue<'r, R> {
type Value<'scale, 'resolver> = Value;
type Error = ValueError;
type TypeResolver = R;
fn visit_i256<'resolver>(
self,
value: &[u8; 32],
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
Ok(Value::I256(*value))
}
fn visit_u256<'resolver>(
self,
value: &[u8; 32],
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
Ok(Value::U256(*value))
}
fn visit_i128<'scale, 'resolver>(
self,
value: i128,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let attempt = value as f64;
if attempt as i128 == value {
Ok(Value::Number(attempt))
} else {
Ok(Value::BigNumber(value.to_string()))
}
}
fn visit_i64<'scale, 'resolver>(
self,
value: i64,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i32<'scale, 'resolver>(
self,
value: i32,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i16<'scale, 'resolver>(
self,
value: i16,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i8<'scale, 'resolver>(
self,
value: i8,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_u128<'scale, 'resolver>(
self,
value: u128,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let attempt = value as f64;
if attempt as u128 == value {
Ok(Value::Number(attempt))
} else {
Ok(Value::BigNumber(value.to_string()))
}
}
fn visit_u64<'scale, 'resolver>(
self,
value: u64,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u32<'scale, 'resolver>(
self,
value: u32,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u16<'scale, 'resolver>(
self,
value: u16,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u8<'scale, 'resolver>(
self,
value: u8,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_bool<'scale, 'resolver>(
self,
value: bool,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Bool(value))
}
fn visit_char<'scale, 'resolver>(
self,
value: char,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Char(value))
}
fn visit_array<'scale, 'resolver>(
self,
values: &mut Array<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_sequence<'scale, 'resolver>(
self,
values: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_str<'scale, 'resolver>(
self,
value: &mut Str<'scale>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::String(value.as_str()?.to_owned()))
}
fn visit_tuple<'scale, 'resolver>(
self,
values: &mut Tuple<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_bitsequence<'scale, 'resolver>(
self,
value: &mut BitSequence<'scale>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let bits = value.decode()?;
let mut out = Vec::with_capacity(bits.len());
for b in bits {
let b = b.map_err(ValueError::CannotDecodeBitSequence)?;
out.push(Value::Bool(b));
}
Ok(Value::Array(out))
}
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
// Special case for ss58 addresses:
if let Some(n) = value.name()
&& n == "AccountId32"
&& value.bytes_from_start().len() == 32
{
return Ok(Value::Address(value.bytes_from_start().to_vec()));
}
// Reuse logic for decoding variant fields:
match to_variant_fieldish(self.resolver, value)? {
VariantFields::Named(s) => Ok(Value::Struct(s)),
VariantFields::Unnamed(a) => Ok(Value::Array(a)),
}
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
// Because we have access to a type resolver on self, we can
// look up the type IDs we're given back and base decode decisions
// on them. here we see whether the enum type has any data attached:
let has_data_visitor = scale_type_resolver::visitor::new((), |_, _| false)
.visit_variant(|_, _, variants| {
for mut variant in variants {
if variant.fields.next().is_some() {
return true;
}
}
false
});
// Do any variants have data in this enum type?
let has_data = self
.resolver
.resolve_type(type_id, has_data_visitor)
.map_err(|e| ValueError::CannotResolveVariantType(e.to_string()))?;
let name = value.name().to_owned();
// base our decoding on whether any data in enum type.
if has_data {
let fields = to_variant_fieldish(self.resolver, value.fields())?;
Ok(Value::VariantWithData(name, fields))
} else {
Ok(Value::VariantWithoutData(name))
}
}
}
fn to_variant_fieldish<'r, 'scale, 'resolver, R: TypeResolver>(
resolver: &'r R,
value: &mut Composite<'scale, 'resolver, R>,
) -> Result<VariantFields, ValueError> {
// If fields are unnamed, treat as array:
if value.fields().iter().all(|f| f.name.is_none()) {
return Ok(VariantFields::Unnamed(to_array(
resolver,
value.remaining(),
value,
)?));
}
// Otherwise object:
let mut out = HashMap::new();
for field in value {
let field = field?;
let name = field.name().unwrap().to_string();
let value = field.decode_with_visitor(GetValue::new(resolver))?;
out.insert(name, value);
}
Ok(VariantFields::Named(out))
}
fn to_array<'r, 'scale, 'resolver, R: TypeResolver>(
resolver: &'r R,
len: usize,
mut values: impl scale_decode::visitor::DecodeItemIterator<'scale, 'resolver, R>,
) -> Result<Vec<Value>, ValueError> {
let mut out = Vec::with_capacity(len);
while let Some(value) = values.decode_item(GetValue::new(resolver)) {
out.push(value?);
}
Ok(out)
}
}
-175
View File
@@ -1,175 +0,0 @@
#![allow(missing_docs)]
use subxt_historic::{OnlineClient, PolkadotConfig, ext::StreamExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
// Configuration for the Polkadot relay chain.
let config = PolkadotConfig::new();
// Create an online client for the Polkadot relay chain, pointed at a Polkadot archive node.
let client = OnlineClient::from_url(config, "wss://rpc.polkadot.io").await?;
// Iterate through some randomly selected blocks to show how to fetch and decode storage.
for block_number in 12345678.. {
println!("=== Block {block_number} ===");
// Point the client at a specific block number. By default this will download and cache
// metadata for the required spec version (so it's cheaper to instantiate again), if it
// hasn't already, and borrow the relevant legacy types from the client.
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")?;
// We can see the default value for this entry at this block, if one exists.
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: [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_as::<scale_value::Value>()?;
// We can visit the value, which is a more advanced use case and allows us to extract more
// data from the type, here the name of it, if it exists:
let tn = entry
.visit(type_name::GetTypeName::new())?
.unwrap_or("<unknown>");
// 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
// access, like so:
#[derive(scale_decode::DecodeAsType)]
struct BalanceInfo {
data: BalanceInfoData,
}
#[derive(scale_decode::DecodeAsType)]
struct BalanceInfoData {
free: u128,
reserved: u128,
misc_frozen: u128,
fee_frozen: u128,
}
let balance_info = entry.decode_as::<BalanceInfo>()?;
println!(
" Single balance info from {account_id_hex} => free: {} reserved: {} misc_frozen: {} fee_frozen: {} (type name: {tn})",
balance_info.data.free,
balance_info.data.reserved,
balance_info.data.misc_frozen,
balance_info.data.fee_frozen,
);
}
// Or we can iterate over all of the account balances and print them out, like so. Here we provide an
// empty tuple, indicating that we want to iterate over everything and not only things under a certain key
// (in the case of account balances, there is only one key anyway, but other storage entries may map from
// several keys to a value, and for those we can choose which depth we iterate at by providing as many keys
// as we want and leaving the rest). Here I only take the first 10 accounts I find for the sake of the example.
let mut all_balances = account_balances.iter(()).await?.take(10);
while let Some(entry) = all_balances.next().await {
let entry = entry?;
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_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.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(())
}
/// This module defines an example visitor which retrieves the name of a type.
/// This is a more advanced use case and can typically be avoided.
mod type_name {
use scale_decode::{
Visitor,
visitor::types::{Composite, Sequence, Variant},
visitor::{TypeIdFor, Unexpected},
};
use scale_type_resolver::TypeResolver;
/// This is a visitor which obtains type names.
pub struct GetTypeName<R> {
marker: core::marker::PhantomData<R>,
}
impl<R> GetTypeName<R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetTypeName {
marker: core::marker::PhantomData,
}
}
}
impl<R: TypeResolver> Visitor for GetTypeName<R> {
type Value<'scale, 'resolver> = Option<&'resolver str>;
type Error = scale_decode::Error;
type TypeResolver = R;
// Look at the path of types that have paths and return the ident from that.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_sequence<'scale, 'resolver>(
self,
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
// Else, we return nothing as we can't find a name for the type.
fn visit_unexpected<'scale, 'resolver>(
self,
_unexpected: Unexpected,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(None)
}
}
}
-73
View File
@@ -1,73 +0,0 @@
mod offline_client;
mod online_client;
use crate::config::Config;
use crate::extrinsics::ExtrinsicsClient;
use crate::storage::StorageClient;
use crate::utils::AnyResolver;
use frame_metadata::RuntimeMetadata;
use std::marker::PhantomData;
pub use offline_client::{OfflineClient, OfflineClientAtBlock, OfflineClientAtBlockT};
pub use online_client::{OnlineClient, OnlineClientAtBlock, OnlineClientAtBlockT};
/// This represents a client at a specific block number.
pub struct ClientAtBlock<Client, T> {
client: Client,
marker: PhantomData<T>,
}
impl<Client, T> ClientAtBlock<Client, T> {
/// Construct a new client at some block.
pub(crate) fn new(client: Client) -> Self {
Self {
client,
marker: PhantomData,
}
}
}
impl<'client, T, Client> ClientAtBlock<Client, T>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
{
/// Work with extrinsics.
pub fn extrinsics(&'_ self) -> ExtrinsicsClient<'_, Client, T> {
ExtrinsicsClient::new(&self.client)
}
/// Work with storage.
pub fn storage(&'_ self) -> StorageClient<'_, Client, T> {
StorageClient::new(&self.client)
}
/// Return the metadata in use at this block.
pub fn metadata(&self) -> &RuntimeMetadata {
self.client.metadata()
}
/// Return something which implements [`scale_type_resolver::TypeResolver`] and
/// can be used in conjnction with type IDs in `.visit` methods.
pub fn resolver(&self) -> AnyResolver<'_, 'client> {
match self.client.metadata() {
RuntimeMetadata::V0(_)
| RuntimeMetadata::V1(_)
| RuntimeMetadata::V2(_)
| RuntimeMetadata::V3(_)
| RuntimeMetadata::V4(_)
| RuntimeMetadata::V5(_)
| RuntimeMetadata::V6(_)
| RuntimeMetadata::V7(_)
| RuntimeMetadata::V8(_)
| RuntimeMetadata::V9(_)
| RuntimeMetadata::V10(_)
| RuntimeMetadata::V11(_)
| RuntimeMetadata::V12(_)
| RuntimeMetadata::V13(_) => AnyResolver::B(self.client.legacy_types()),
RuntimeMetadata::V14(m) => AnyResolver::A(&m.types),
RuntimeMetadata::V15(m) => AnyResolver::A(&m.types),
RuntimeMetadata::V16(m) => AnyResolver::A(&m.types),
}
}
}
-83
View File
@@ -1,83 +0,0 @@
use super::ClientAtBlock;
use crate::config::Config;
use crate::error::OfflineClientAtBlockError;
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::TypeRegistrySet;
use std::sync::Arc;
/// A client which exposes the means to decode historic data on a chain offline.
#[derive(Clone, Debug)]
pub struct OfflineClient<T: Config> {
/// The configuration for this client.
config: Arc<T>,
}
impl<T: Config> OfflineClient<T> {
/// Create a new [`OfflineClient`] with the given configuration.
pub fn new(config: T) -> Self {
OfflineClient {
config: Arc::new(config),
}
}
/// Pick the block height at which to operate. This references data from the
/// [`OfflineClient`] it's called on, and so cannot outlive it.
pub fn at<'this>(
&'this self,
block_number: u64,
) -> Result<ClientAtBlock<OfflineClientAtBlock<'this, T>, T>, OfflineClientAtBlockError> {
let config = &self.config;
let spec_version = self
.config
.spec_version_for_block_number(block_number)
.ok_or(OfflineClientAtBlockError::SpecVersionNotFound { block_number })?;
let legacy_types = self.config.legacy_types_for_spec_version(spec_version);
let metadata = self
.config
.metadata_for_spec_version(spec_version)
.ok_or(OfflineClientAtBlockError::MetadataNotFound { spec_version })?;
Ok(ClientAtBlock::new(OfflineClientAtBlock {
config,
legacy_types,
metadata,
}))
}
}
/// This represents an offline-only client at a specific block.
pub trait OfflineClientAtBlockT<'client, T: Config + 'client> {
/// Get the configuration for this client.
fn config(&self) -> &'client T;
/// Get the legacy types that work at this block.
fn legacy_types(&'_ self) -> &TypeRegistrySet<'client>;
/// Get the metadata appropriate for this block.
fn metadata(&self) -> &RuntimeMetadata;
}
// Dev note: this shouldn't need to be exposed unless there is some
// need to explicitly name the ClientAAtBlock type. Rather keep it
// private to allow changes if possible.
pub struct OfflineClientAtBlock<'client, T: Config + 'client> {
/// The configuration for this chain.
config: &'client T,
/// Historic types to use at this block number.
legacy_types: TypeRegistrySet<'client>,
/// Metadata to use at this block number.
metadata: Arc<RuntimeMetadata>,
}
impl<'client, T: Config + 'client> OfflineClientAtBlockT<'client, T>
for OfflineClientAtBlock<'client, T>
{
fn config(&self) -> &'client T {
self.config
}
fn legacy_types(&self) -> &TypeRegistrySet<'client> {
&self.legacy_types
}
fn metadata(&self) -> &RuntimeMetadata {
&self.metadata
}
}
-331
View File
@@ -1,331 +0,0 @@
use super::ClientAtBlock;
use crate::client::OfflineClientAtBlockT;
use crate::config::Config;
use crate::error::OnlineClientAtBlockError;
use codec::{Compact, Decode, Encode};
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
use scale_info_legacy::TypeRegistrySet;
use std::sync::Arc;
use subxt_rpcs::methods::chain_head::ArchiveCallResult;
use subxt_rpcs::{ChainHeadRpcMethods, RpcClient};
#[cfg(feature = "jsonrpsee")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonrpsee")))]
use crate::error::OnlineClientError;
/// A client which exposes the means to decode historic data on a chain online.
#[derive(Clone, Debug)]
pub struct OnlineClient<T: Config> {
inner: Arc<OnlineClientInner<T>>,
}
#[derive(Debug)]
struct OnlineClientInner<T: Config> {
/// The configuration for this client.
config: T,
/// The RPC methods used to communicate with the node.
rpc_methods: ChainHeadRpcMethods<T>,
}
// The default constructors assume Jsonrpsee.
#[cfg(feature = "jsonrpsee")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonrpsee")))]
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`.
///
/// **Note:** This will only work if the local node is an archive node.
pub async fn new(config: T) -> Result<OnlineClient<T>, OnlineClientError> {
let url = "ws://127.0.0.1:9944";
OnlineClient::from_url(config, url).await
}
/// Construct a new [`OnlineClient`], providing a URL to connect to.
pub async fn from_url(
config: T,
url: impl AsRef<str>,
) -> Result<OnlineClient<T>, OnlineClientError> {
let url_str = url.as_ref();
let url = url::Url::parse(url_str).map_err(|_| OnlineClientError::InvalidUrl {
url: url_str.to_string(),
})?;
if !Self::is_url_secure(&url) {
return Err(OnlineClientError::RpcClientError(
subxt_rpcs::Error::InsecureUrl(url_str.to_string()),
));
}
OnlineClient::from_insecure_url(config, url).await
}
/// 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(
config: T,
url: impl AsRef<str>,
) -> Result<OnlineClient<T>, OnlineClientError> {
let rpc_client = RpcClient::from_insecure_url(url).await?;
Ok(OnlineClient::from_rpc_client(config, rpc_client))
}
fn is_url_secure(url: &url::Url) -> bool {
let secure_scheme = url.scheme() == "https" || url.scheme() == "wss";
let is_localhost = url.host().is_some_and(|e| match e {
url::Host::Domain(e) => e == "localhost",
url::Host::Ipv4(e) => e.is_loopback(),
url::Host::Ipv6(e) => e.is_loopback(),
});
secure_scheme || is_localhost
}
}
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] by providing an [`RpcClient`] to drive the connection,
/// and some configuration for the chain we're connecting to.
pub fn from_rpc_client(config: T, rpc_client: impl Into<RpcClient>) -> OnlineClient<T> {
let rpc_client = rpc_client.into();
let rpc_methods = ChainHeadRpcMethods::new(rpc_client);
OnlineClient {
inner: Arc::new(OnlineClientInner {
config,
rpc_methods,
}),
}
}
/// Pick the block height at which to operate. This references data from the
/// [`OnlineClient`] it's called on, and so cannot outlive it.
pub async fn at(
&'_ self,
block_number: u64,
) -> Result<ClientAtBlock<OnlineClientAtBlock<'_, T>, T>, OnlineClientAtBlockError> {
let config = &self.inner.config;
let rpc_methods = &self.inner.rpc_methods;
let block_hash = rpc_methods
.archive_v1_hash_by_height(block_number as usize)
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetBlockHash {
block_number,
reason: e,
})?
.pop()
.ok_or_else(|| OnlineClientAtBlockError::BlockNotFound { block_number })?
.into();
// Get our configuration, or fetch from the node if not available.
let spec_version =
if let Some(spec_version) = config.spec_version_for_block_number(block_number) {
spec_version
} else {
// Fetch spec version. Caching this doesn't really make sense, so either
// details are provided offline or we fetch them every time.
get_spec_version(rpc_methods, block_hash).await?
};
let metadata = if let Some(metadata) = config.metadata_for_spec_version(spec_version) {
metadata
} else {
// Fetch and then give our config the opportunity to cache this metadata.
let metadata = get_metadata(rpc_methods, block_hash).await?;
let metadata = Arc::new(metadata);
config.set_metadata_for_spec_version(spec_version, metadata.clone());
metadata
};
let mut historic_types = config.legacy_types_for_spec_version(spec_version);
// The metadata can be used to construct call and event types instead of us having to hardcode them all for every spec version:
let types_from_metadata = frame_decode::helpers::type_registry_from_metadata_any(&metadata)
.map_err(
|parse_error| OnlineClientAtBlockError::CannotInjectMetadataTypes { parse_error },
)?;
historic_types.prepend(types_from_metadata);
Ok(ClientAtBlock::new(OnlineClientAtBlock {
config,
historic_types,
metadata,
rpc_methods,
block_hash,
}))
}
}
/// This represents an online client at a specific block.
pub trait OnlineClientAtBlockT<'client, T: Config + 'client>:
OfflineClientAtBlockT<'client, T>
{
/// Return the RPC methods we'll use to interact with the node.
fn rpc_methods(&self) -> &ChainHeadRpcMethods<T>;
/// Return the block hash for the current block.
fn block_hash(&self) -> <T as Config>::Hash;
}
// Dev note: this shouldn't need to be exposed unless there is some
// need to explicitly name the ClientAAtBlock type. Rather keep it
// private to allow changes if possible.
pub struct OnlineClientAtBlock<'client, T: Config + 'client> {
/// The configuration for this chain.
config: &'client T,
/// Historic types to use at this block number.
historic_types: TypeRegistrySet<'client>,
/// Metadata to use at this block number.
metadata: Arc<RuntimeMetadata>,
/// We also need RPC methods for online interactions.
rpc_methods: &'client ChainHeadRpcMethods<T>,
/// The block hash at which this client is operating.
block_hash: <T as Config>::Hash,
}
impl<'client, T: Config + 'client> OnlineClientAtBlockT<'client, T>
for OnlineClientAtBlock<'client, T>
{
fn rpc_methods(&self) -> &ChainHeadRpcMethods<T> {
self.rpc_methods
}
fn block_hash(&self) -> <T as Config>::Hash {
self.block_hash
}
}
impl<'client, T: Config + 'client> OfflineClientAtBlockT<'client, T>
for OnlineClientAtBlock<'client, T>
{
fn config(&self) -> &'client T {
self.config
}
fn legacy_types(&'_ self) -> &TypeRegistrySet<'client> {
&self.historic_types
}
fn metadata(&self) -> &RuntimeMetadata {
&self.metadata
}
}
async fn get_spec_version<T: Config>(
rpc_methods: &ChainHeadRpcMethods<T>,
block_hash: <T as Config>::Hash,
) -> Result<u32, OnlineClientAtBlockError> {
use codec::Decode;
use subxt_rpcs::methods::chain_head::ArchiveCallResult;
// make a runtime call to get the version information. This is also a constant
// in the metadata and so we could fetch it from there to avoid the call, but it would be a
// bit more effort.
let spec_version_bytes = {
let call_res = rpc_methods
.archive_v1_call(block_hash.into(), "Core_version", &[])
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetSpecVersion {
block_hash: block_hash.to_string(),
reason: format!("Error calling Core_version: {e}"),
})?;
match call_res {
ArchiveCallResult::Success(bytes) => bytes.0,
ArchiveCallResult::Error(e) => {
return Err(OnlineClientAtBlockError::CannotGetSpecVersion {
block_hash: block_hash.to_string(),
reason: format!("Core_version returned an error: {e}"),
});
}
}
};
// We only care about the spec version, so just decode enough of this version information
// to be able to pluck out what we want, and ignore the rest.
let spec_version = {
#[derive(codec::Decode)]
struct SpecVersionHeader {
_spec_name: String,
_impl_name: String,
_authoring_version: u32,
spec_version: u32,
}
SpecVersionHeader::decode(&mut &spec_version_bytes[..])
.map_err(|e| OnlineClientAtBlockError::CannotGetSpecVersion {
block_hash: block_hash.to_string(),
reason: format!("Error decoding Core_version response: {e}"),
})?
.spec_version
};
Ok(spec_version)
}
async fn get_metadata<T: Config>(
rpc_methods: &ChainHeadRpcMethods<T>,
block_hash: <T as Config>::Hash,
) -> Result<RuntimeMetadata, OnlineClientAtBlockError> {
// First, try to use the "modern" metadata APIs to get the most recent version we can.
let version_to_get = rpc_methods
.archive_v1_call(block_hash.into(), "Metadata_metadata_versions", &[])
.await
.ok()
.and_then(|res| res.as_success())
.and_then(|res| <Vec<u32>>::decode(&mut &res[..]).ok())
.and_then(|versions| {
// We want to filter out the "unstable" version, which is represented by u32::MAX.
versions.into_iter().filter(|v| *v != u32::MAX).max()
});
// We had success calling the above API, so we expect the "modern" metadata API to work.
if let Some(version_to_get) = version_to_get {
let version_bytes = version_to_get.encode();
let rpc_response = rpc_methods
.archive_v1_call(
block_hash.into(),
"Metadata_metadata_at_version",
&version_bytes,
)
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error calling Metadata_metadata_at_version: {e}"),
})
.and_then(|res| match res {
ArchiveCallResult::Success(bytes) => Ok(bytes.0),
ArchiveCallResult::Error(e) => Err(OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Calling Metadata_metadata_at_version returned an error: {e}"),
}),
})?;
// Option because we may have asked for a version that doesn't exist. Compact because we get back a Vec<u8>
// of the metadata bytes, and the Vec is preceded by it's compact encoded length. The actual bytes are then
// decoded as a `RuntimeMetadataPrefixed`, after this.
let (_, metadata) = <Option<(Compact<u32>, RuntimeMetadataPrefixed)>>::decode(&mut &rpc_response[..])
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error decoding response for Metadata_metadata_at_version: {e}"),
})?
.ok_or_else(|| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("No metadata returned for the latest version from Metadata_metadata_versions ({version_to_get})"),
})?;
return Ok(metadata.1);
}
// We didn't get a version from Metadata_metadata_versions, so fall back to the "old" API.
let metadata_bytes = rpc_methods
.archive_v1_call(block_hash.into(), "Metadata_metadata", &[])
.await
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error calling Metadata_metadata: {e}"),
})
.and_then(|res| match res {
ArchiveCallResult::Success(bytes) => Ok(bytes.0),
ArchiveCallResult::Error(e) => Err(OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Calling Metadata_metadata returned an error: {e}"),
}),
})?;
let (_, metadata) = <(Compact<u32>, RuntimeMetadataPrefixed)>::decode(&mut &metadata_bytes[..])
.map_err(|e| OnlineClientAtBlockError::CannotGetMetadata {
block_hash: block_hash.to_string(),
reason: format!("Error decoding response for Metadata_metadata: {e}"),
})?;
Ok(metadata.1)
}
-56
View File
@@ -1,56 +0,0 @@
pub mod polkadot;
pub mod substrate;
use scale_info_legacy::TypeRegistrySet;
use std::fmt::Display;
use std::sync::Arc;
use subxt_rpcs::RpcConfig;
pub use polkadot::PolkadotConfig;
pub use substrate::SubstrateConfig;
/// This represents the configuration needed for a specific chain. This includes
/// any hardcoded types we need to know about for that chain, as well as a means to
/// obtain historic types for that chain.
pub trait Config: RpcConfig {
/// The type of hashing used by the runtime.
type Hash: Clone
+ Copy
+ Display
+ Into<<Self as RpcConfig>::Hash>
+ From<<Self as RpcConfig>::Hash>;
/// Return the spec version for a given block number, if available.
///
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here,
/// but the [`crate::client::OfflineClient`] will error if this is not available for the required block number.
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32>;
/// Return the metadata for a given spec version, if available.
///
/// The [`crate::client::OnlineClient`] will look this up on chain if it's not available here, and then
/// call [`Config::set_metadata_for_spec_version`] to give the configuration the opportunity to cache it.
/// The [`crate::client::OfflineClient`] will error if this is not available for the required spec version.
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<frame_metadata::RuntimeMetadata>>;
/// Set some metadata for a given spec version. the [`crate::client::OnlineClient`] will call this if it has
/// to retrieve metadata from the chain, to give this the opportunity to cache it. The configuration can
/// do nothing if it prefers.
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<frame_metadata::RuntimeMetadata>,
);
/// Return legacy types (ie types to use with Runtimes that return pre-V14 metadata) for a given spec version.
fn legacy_types_for_spec_version<'this>(
&'this self,
spec_version: u32,
) -> TypeRegistrySet<'this>;
/// Hash some bytes, for instance a block header or extrinsic, for this chain.
fn hash(s: &[u8]) -> <Self as Config>::Hash;
}
-88
View File
@@ -1,88 +0,0 @@
use super::Config;
use super::SubstrateConfig;
use scale_info_legacy::{ChainTypeRegistry, TypeRegistrySet};
use std::sync::Arc;
/// Configuration that's suitable for the Polkadot Relay Chain
pub struct PolkadotConfig(SubstrateConfig);
impl PolkadotConfig {
/// Create a new PolkadotConfig.
pub fn new() -> Self {
let config = SubstrateConfig::new()
.set_legacy_types(frame_decode::legacy_types::polkadot::relay_chain());
// TODO: Set spec versions as well with known spec version changes, to speed
// up accessing historic blocks within the known ranges. For now, we just let
// the online client look these up on chain.
Self(config)
}
/// Set the metadata to be used for decoding blocks at the given spec versions.
pub fn set_metadata_for_spec_versions(
mut self,
ranges: impl Iterator<Item = (u32, frame_metadata::RuntimeMetadata)>,
) -> Self {
self = Self(self.0.set_metadata_for_spec_versions(ranges));
self
}
/// Given an iterator of block ranges to spec version of the form `(start, end, spec_version)`, add them
/// to this configuration.
pub fn set_spec_version_for_block_ranges(
mut self,
ranges: impl Iterator<Item = (u64, u64, u32)>,
) -> Self {
self = Self(self.0.set_spec_version_for_block_ranges(ranges));
self
}
}
/// This hands back the legacy types for the Polkadot Relay Chain, which is what [`PolkadotConfig`] uses internally.
pub fn legacy_types() -> ChainTypeRegistry {
frame_decode::legacy_types::polkadot::relay_chain()
}
impl Default for PolkadotConfig {
fn default() -> Self {
Self::new()
}
}
impl Config for PolkadotConfig {
type Hash = <SubstrateConfig as Config>::Hash;
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> TypeRegistrySet<'_> {
self.0.legacy_types_for_spec_version(spec_version)
}
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32> {
self.0.spec_version_for_block_number(block_number)
}
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<frame_metadata::RuntimeMetadata>> {
self.0.metadata_for_spec_version(spec_version)
}
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<frame_metadata::RuntimeMetadata>,
) {
self.0.set_metadata_for_spec_version(spec_version, metadata)
}
fn hash(s: &[u8]) -> <Self as Config>::Hash {
SubstrateConfig::hash(s)
}
}
impl subxt_rpcs::RpcConfig for PolkadotConfig {
type Hash = <SubstrateConfig as subxt_rpcs::RpcConfig>::Hash;
type Header = <SubstrateConfig as subxt_rpcs::RpcConfig>::Header;
type AccountId = <SubstrateConfig as subxt_rpcs::RpcConfig>::AccountId;
}
-129
View File
@@ -1,129 +0,0 @@
use super::Config;
use crate::utils::RangeMap;
use primitive_types::H256;
use scale_info_legacy::{ChainTypeRegistry, TypeRegistrySet};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
/// Configuration that's suitable for standard Substrate chains (ie those
/// that have not customized the block hash type).
pub struct SubstrateConfig {
legacy_types: ChainTypeRegistry,
spec_version_for_block_number: RangeMap<u64, u32>,
metadata_for_spec_version: Mutex<HashMap<u32, Arc<frame_metadata::RuntimeMetadata>>>,
}
impl SubstrateConfig {
/// Create a new SubstrateConfig with no legacy types.
///
/// Without any further configuration, this will only work with
/// the [`crate::client::OnlineClient`] for blocks that were produced by Runtimes
/// that emit metadata V14 or later.
///
/// To support working at any block with the [`crate::client::OnlineClient`], you
/// must call [`SubstrateConfig::set_legacy_types`] with appropriate legacy type
/// definitions.
///
/// To support working with the [`crate::client::OfflineClient`] at any block,
/// you must also call:
/// - [`SubstrateConfig::set_metadata_for_spec_versions`] to set the metadata to
/// use at each spec version we might encounter.
/// - [`SubstrateConfig::set_spec_version_for_block_ranges`] to set the spec version
/// to use for each range of blocks we might encounter.
pub fn new() -> Self {
Self {
legacy_types: ChainTypeRegistry::empty(),
spec_version_for_block_number: RangeMap::empty(),
metadata_for_spec_version: Mutex::new(HashMap::new()),
}
}
/// Set the legacy types to use for this configuration. This enables support for
/// blocks produced by Runtimes that emit metadata older than V14.
pub fn set_legacy_types(mut self, legacy_types: ChainTypeRegistry) -> Self {
self.legacy_types = legacy_types;
self
}
/// Set the metadata to be used for decoding blocks at the given spec versions.
pub fn set_metadata_for_spec_versions(
self,
ranges: impl Iterator<Item = (u32, frame_metadata::RuntimeMetadata)>,
) -> Self {
let mut map = self.metadata_for_spec_version.lock().unwrap();
for (spec_version, metadata) in ranges {
map.insert(spec_version, Arc::new(metadata));
}
drop(map);
self
}
/// Given an iterator of block ranges to spec version of the form `(start, end, spec_version)`, add them
/// to this configuration.
pub fn set_spec_version_for_block_ranges(
mut self,
ranges: impl Iterator<Item = (u64, u64, u32)>,
) -> Self {
let mut m = RangeMap::builder();
for (start, end, spec_version) in ranges {
m = m.add_range(start, end, spec_version);
}
self.spec_version_for_block_number = m.build();
self
}
}
impl Default for SubstrateConfig {
fn default() -> Self {
Self::new()
}
}
impl Config for SubstrateConfig {
type Hash = H256;
fn legacy_types_for_spec_version(&'_ self, spec_version: u32) -> TypeRegistrySet<'_> {
self.legacy_types.for_spec_version(spec_version as u64)
}
fn spec_version_for_block_number(&self, block_number: u64) -> Option<u32> {
self.spec_version_for_block_number
.get(block_number)
.copied()
}
fn metadata_for_spec_version(
&self,
spec_version: u32,
) -> Option<Arc<frame_metadata::RuntimeMetadata>> {
self.metadata_for_spec_version
.lock()
.unwrap()
.get(&spec_version)
.cloned()
}
fn set_metadata_for_spec_version(
&self,
spec_version: u32,
metadata: Arc<frame_metadata::RuntimeMetadata>,
) {
self.metadata_for_spec_version
.lock()
.unwrap()
.insert(spec_version, metadata);
}
fn hash(s: &[u8]) -> <Self as Config>::Hash {
sp_crypto_hashing::blake2_256(s).into()
}
}
impl subxt_rpcs::RpcConfig for SubstrateConfig {
type Hash = <Self as Config>::Hash;
// We don't use these types in any of the RPC methods we call,
// so don't bother setting them up:
type Header = ();
type AccountId = ();
}
-325
View File
@@ -1,325 +0,0 @@
/// Any error emitted by this crate can convert into this.
// Dev Note: All errors here are transparent, because in many places
// the inner errors are returned and so need to provide enough context
// as-is, so there shouldn't be anything to add here.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
OnlineClientError(#[from] OnlineClientError),
#[error(transparent)]
OfflineClientAtBlockError(#[from] OfflineClientAtBlockError),
#[error(transparent)]
OnlineClientAtBlockError(#[from] OnlineClientAtBlockError),
#[error(transparent)]
ExtrinsicsError(#[from] ExtrinsicsError),
#[error(transparent)]
ExtrinsicTransactionExtensionError(#[from] ExtrinsicTransactionExtensionError),
#[error(transparent)]
ExtrinsicCallError(#[from] ExtrinsicCallError),
#[error(transparent)]
StorageError(#[from] StorageError),
#[error(transparent)]
StorageKeyError(#[from] StorageKeyError),
#[error(transparent)]
StorageValueError(#[from] StorageValueError),
}
/// Errors constructing an online client.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OnlineClientError {
#[error("Cannot construct OnlineClient: The URL provided is invalid: {url}")]
InvalidUrl {
/// The URL that was invalid.
url: String,
},
#[error("Cannot construct OnlineClient owing to an RPC client error: {0}")]
RpcClientError(#[from] subxt_rpcs::Error),
}
/// Errors constructing an offline client at a specific block number.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OfflineClientAtBlockError {
#[error(
"Cannot construct OfflineClientAtBlock: spec version not found for block number {block_number}"
)]
SpecVersionNotFound {
/// The block number for which the spec version was not found.
block_number: u64,
},
#[error(
"Cannot construct OfflineClientAtBlock: metadata not found for spec version {spec_version}"
)]
MetadataNotFound {
/// The spec version for which the metadata was not found.
spec_version: u32,
},
}
/// Errors constructing an online client at a specific block number.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OnlineClientAtBlockError {
#[error(
"Cannot construct OnlineClientAtBlock: failed to get block hash from node for block {block_number}: {reason}"
)]
CannotGetBlockHash {
/// Block number we failed to get the hash for.
block_number: u64,
/// The error we encountered.
reason: subxt_rpcs::Error,
},
#[error("Cannot construct OnlineClientAtBlock: block number {block_number} not found")]
BlockNotFound {
/// The block number for which a block was not found.
block_number: u64,
},
#[error(
"Cannot construct OnlineClientAtBlock: failed to get spec version for block hash {block_hash}: {reason}"
)]
CannotGetSpecVersion {
/// The block hash for which we failed to get the spec version.
block_hash: String,
/// The error we encountered.
reason: String,
},
#[error(
"Cannot construct OnlineClientAtBlock: failed to get metadata for block hash {block_hash}: {reason}"
)]
CannotGetMetadata {
/// The block hash for which we failed to get the metadata.
block_hash: String,
/// The error we encountered.
reason: String,
},
#[error(
"Cannot inject types from metadata: failure to parse a type found in the metadata: {parse_error}"
)]
CannotInjectMetadataTypes {
/// Error parsing a type found in the metadata.
parse_error: scale_info_legacy::lookup_name::ParseError,
},
}
/// Errors working with extrinsics.
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ExtrinsicsError {
#[error("Could not fetch extrinsics: {reason}")]
FetchError {
/// The error that occurred while fetching the extrinsics.
reason: subxt_rpcs::Error,
},
#[error("Could not decode extrinsic at index {index}: {reason}")]
DecodeError {
/// The extrinsic index that failed to decode.
index: usize,
/// The error that occurred during decoding.
reason: frame_decode::extrinsics::ExtrinsicDecodeError,
},
#[error(
"Could not decode extrinsic at index {index}: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The extrinsic index that had leftover bytes
index: usize,
/// The bytes that were left over after decoding the extrinsic.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode extrinsics: Unsupported metadata version ({version})")]
UnsupportedMetadataVersion {
/// The metadata version that is not supported.
version: u32,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ExtrinsicTransactionExtensionError {
#[error("Could not decode extrinsic transaction extensions: {reason}")]
AllDecodeError {
/// The error that occurred while decoding the transaction extensions.
reason: scale_decode::Error,
},
#[error(
"Could not decode extrinsic transaction extensions: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
AllLeftoverBytes {
/// The bytes that were left over after decoding the transaction extensions.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode extrinsic transaction extension {name}: {reason}")]
DecodeError {
/// The name of the transaction extension that failed to decode.
name: String,
/// The error that occurred during decoding.
reason: scale_decode::Error,
},
#[error(
"Could not decode extrinsic transaction extension {name}: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The name of the transaction extension that had leftover bytes.
name: String,
/// The bytes that were left over after decoding the transaction extension.
leftover_bytes: Vec<u8>,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ExtrinsicCallError {
#[error("Could not decode the fields in extrinsic call: {reason}")]
FieldsDecodeError {
/// The error that occurred while decoding the fields of the extrinsic call.
reason: scale_decode::Error,
},
#[error(
"Could not decode the fields in extrinsic call: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
FieldsLeftoverBytes {
/// The bytes that were left over after decoding the extrinsic call.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode field {name} in extrinsic call: {reason}")]
FieldDecodeError {
/// The name of the field that failed to decode.
name: String,
/// The error that occurred during decoding.
reason: scale_decode::Error,
},
#[error(
"Could not decode field {name} in extrinsic call: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
FieldLeftoverBytes {
/// The name of the field that had leftover bytes.
name: String,
/// The bytes that were left over after decoding the extrinsic call.
leftover_bytes: Vec<u8>,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[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 entry_name: String,
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[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 entry_name: String,
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageError {
#[error("RPC error interacting with storage APIs: {reason}")]
RpcError {
/// The error that occurred while fetching the storage entry.
reason: subxt_rpcs::Error,
},
#[error("Could not fetch next entry from storage subscription: {reason}")]
StorageEventError {
/// The error that occurred while fetching the next storage entry.
reason: String,
},
#[error("Could not construct storage key: {reason}")]
KeyEncodeError {
/// The error that occurred while constructing the storage key.
reason: frame_decode::storage::StorageKeyEncodeError,
},
#[error(
"Wrong number of keys provided to fetch a value: expected {num_keys_expected} keys, but got {num_keys_provided}"
)]
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})"
)]
UnsupportedMetadataVersion {
/// The metadata version that is not supported.
version: u32,
},
#[error("Could not extract storage information from metadata: {reason}")]
ExtractStorageInfoError {
/// The error that occurred while extracting storage information from the metadata.
reason: frame_decode::storage::StorageInfoError<'static>,
},
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageKeyError {
#[error("Could not decode the storage key: {reason}")]
DecodeError {
/// The error that occurred while decoding the storage key information.
reason: frame_decode::storage::StorageKeyDecodeError<String>,
},
#[error(
"Could not decode the storage key: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The bytes that were left over after decoding the storage key.
leftover_bytes: Vec<u8>,
},
#[error("Could not decode the part of the storage key at index {index}: {reason}")]
DecodePartError {
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)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StorageValueError {
#[error("Could not decode storage value: {reason}")]
DecodeError {
/// The error that occurred while decoding the storage value.
reason: scale_decode::Error,
},
#[error(
"Could not decode storage value: there were undecoded bytes at the end, which implies that we did not decode it properly"
)]
LeftoverBytes {
/// The bytes that were left over after decoding the storage value.
leftover_bytes: Vec<u8>,
},
}
-76
View File
@@ -1,76 +0,0 @@
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
use crate::config::Config;
use crate::error::ExtrinsicsError;
mod extrinsic_call;
mod extrinsic_info;
mod extrinsic_transaction_extensions;
mod extrinsics_type;
pub use extrinsic_transaction_extensions::ExtrinsicTransactionParams;
pub use extrinsics_type::{Extrinsic, Extrinsics};
/// Work with extrinsics.
pub struct ExtrinsicsClient<'atblock, Client, T> {
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> ExtrinsicsClient<'atblock, Client, T> {
/// Work with extrinsics.
pub(crate) fn new(client: &'atblock Client) -> Self {
Self {
client,
marker: std::marker::PhantomData,
}
}
}
// Things that we can do online with extrinsics.
impl<'atblock, 'client: 'atblock, Client, T> ExtrinsicsClient<'atblock, Client, T>
where
T: Config + 'client,
Client: OnlineClientAtBlockT<'client, T>,
{
/// Fetch the extrinsics for the current block. This is essentially a
/// combination of [`Self::fetch_bytes`] and [`Self::decode_from`].
pub async fn fetch(&self) -> Result<Extrinsics<'atblock>, ExtrinsicsError> {
let bytes: Vec<Vec<u8>> = self.fetch_bytes().await?;
// Small optimization; no need to decode anything if no bytes.
if bytes.is_empty() {
return Ok(Extrinsics::empty());
}
self.decode_from(bytes)
}
/// Fetch the bytes for the extrinsics in the current block.
pub async fn fetch_bytes(&self) -> Result<Vec<Vec<u8>>, ExtrinsicsError> {
let bytes: Vec<Vec<u8>> = self
.client
.rpc_methods()
.archive_v1_body(self.client.block_hash().into())
.await
.map_err(|e| ExtrinsicsError::FetchError { reason: e })?
.map(|body| body.into_iter().map(|b| b.0).collect())
.unwrap_or_default();
Ok(bytes)
}
}
// Things that we can do offline with extrinsics.
impl<'atblock, 'client: 'atblock, Client, T> ExtrinsicsClient<'atblock, Client, T>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
{
/// Given some bytes representing the extrinsics in this block, decode them into an [`Extrinsics`] type.
pub fn decode_from(
&self,
bytes: Vec<Vec<u8>>,
) -> Result<Extrinsics<'atblock>, ExtrinsicsError> {
Extrinsics::new(bytes, self.client)
}
}
-210
View File
@@ -1,210 +0,0 @@
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
use crate::error::ExtrinsicCallError;
use crate::utils::Either;
use crate::utils::{AnyResolver, AnyTypeId};
use scale_info_legacy::{LookupName, TypeRegistrySet};
/// This represents the call data in the extrinsic.
pub struct ExtrinsicCall<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicCall<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
) -> Self {
Self { all_bytes, info }
}
/// The index of the pallet that this call is for
pub fn pallet_index(&self) -> u8 {
with_info!(&self.info => info.info.pallet_index())
}
/// The name of the pallet that this call is for.
pub fn pallet_name(&self) -> &str {
with_info!(&self.info => info.info.pallet_name())
}
/// The index of this call.
pub fn index(&self) -> u8 {
with_info!(&self.info => info.info.call_index())
}
/// The name of this call.
pub fn name(&self) -> &str {
with_info!(&self.info => info.info.call_name())
}
/// Get the raw bytes for the entire call, which includes the pallet and call index
/// bytes as well as the encoded arguments for each of the fields.
pub fn bytes(&self) -> &'extrinsics [u8] {
with_info!(&self.info => &self.all_bytes[info.info.call_data_range()])
}
/// Work with the fields in this call.
pub fn fields(&self) -> ExtrinsicCallFields<'extrinsics, 'atblock> {
ExtrinsicCallFields::new(self.all_bytes, self.info)
}
}
/// This represents the fields of the call.
pub struct ExtrinsicCallFields<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
resolver: AnyResolver<'atblock, 'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
) -> Self {
let resolver = match info {
AnyExtrinsicInfo::Legacy(info) => AnyResolver::B(info.resolver),
AnyExtrinsicInfo::Current(info) => AnyResolver::A(info.resolver),
};
Self {
all_bytes,
info,
resolver,
}
}
/// Return the bytes representing the fields stored in this extrinsic.
///
/// # Note
///
/// This is a subset of [`ExtrinsicCall::bytes`] that does not include the
/// first two bytes that denote the pallet index and the variant index.
pub fn bytes(&self) -> &'extrinsics [u8] {
with_info!(&self.info => &self.all_bytes[info.info.call_data_args_range()])
}
/// Iterate over each of the fields of the extrinsic call data.
pub fn iter(&self) -> impl Iterator<Item = ExtrinsicCallField<'_, 'extrinsics, 'atblock>> {
match &self.info {
AnyExtrinsicInfo::Legacy(info) => {
Either::A(info.info.call_data().map(|named_arg| ExtrinsicCallField {
field_bytes: &self.all_bytes[named_arg.range()],
resolver: &self.resolver,
info: AnyExtrinsicCallFieldInfo::Legacy(ExtrinsicCallFieldInfo {
info: named_arg,
resolver: info.resolver,
}),
}))
}
AnyExtrinsicInfo::Current(info) => {
Either::B(info.info.call_data().map(|named_arg| ExtrinsicCallField {
field_bytes: &self.all_bytes[named_arg.range()],
resolver: &self.resolver,
info: AnyExtrinsicCallFieldInfo::Current(ExtrinsicCallFieldInfo {
info: named_arg,
resolver: info.resolver,
}),
}))
}
}
}
/// Attempt to decode the fields into the given type.
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| {
scale_decode::Field::new(named_arg.ty().clone(), Some(named_arg.name()))
});
let decoded = T::decode_as_fields(cursor, &mut fields, info.resolver)
.map_err(|e| ExtrinsicCallError::FieldsDecodeError { reason: e })?;
if !cursor.is_empty() {
return Err(ExtrinsicCallError::FieldsLeftoverBytes {
leftover_bytes: cursor.to_vec(),
})
}
Ok(decoded)
})
}
}
pub struct ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
field_bytes: &'extrinsics [u8],
info: AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock>,
resolver: &'fields AnyResolver<'atblock, 'atblock>,
}
enum AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock> {
Legacy(ExtrinsicCallFieldInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicCallFieldInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>),
}
struct ExtrinsicCallFieldInfo<'extrinsics, 'atblock, TypeId, Resolver> {
info: &'extrinsics frame_decode::extrinsics::NamedArg<'atblock, TypeId>,
resolver: &'atblock Resolver,
}
macro_rules! with_call_field_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicCallFieldInfo::Legacy($info) => $fn,
AnyExtrinsicCallFieldInfo::Current($info) => $fn,
}
};
}
impl<'fields, 'extrinsics, 'atblock> ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
/// Get the raw bytes for this field.
pub fn bytes(&self) -> &'extrinsics [u8] {
self.field_bytes
}
/// Get the name of this field.
pub fn name(&self) -> &'extrinsics str {
with_call_field_info!(&self.info => info.info.name())
}
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
/// version of [`ExtrinsicCallField::decode_as`], as the visitor is able to preserve lifetimes
/// and has access to more type information than is available via [`ExtrinsicCallField::decode_as`].
pub fn visit<
V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock, 'atblock>>,
>(
&self,
visitor: V,
) -> Result<V::Value<'extrinsics, 'fields>, V::Error> {
let type_id = match &self.info {
AnyExtrinsicCallFieldInfo::Current(info) => AnyTypeId::A(*info.info.ty()),
AnyExtrinsicCallFieldInfo::Legacy(info) => AnyTypeId::B(info.info.ty().clone()),
};
let cursor = &mut self.bytes();
scale_decode::visitor::decode_with_visitor(cursor, type_id, self.resolver, visitor)
}
/// Attempt to decode the value of this field into the given type.
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)
.map_err(|e| ExtrinsicCallError::FieldDecodeError {
name: info.info.name().to_string(),
reason: e,
})?;
if !cursor.is_empty() {
return Err(ExtrinsicCallError::FieldLeftoverBytes {
name: info.info.name().to_string(),
leftover_bytes: cursor.to_vec(),
});
}
Ok(decoded)
})
}
}
-109
View File
@@ -1,109 +0,0 @@
use crate::error::ExtrinsicsError;
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::{LookupName, TypeRegistrySet};
// Extrinsic information for modern or legacy extrinsics.
#[allow(clippy::large_enum_variant)]
pub enum AnyExtrinsicInfo<'atblock> {
Legacy(ExtrinsicInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicInfo<'atblock, u32, scale_info::PortableRegistry>),
}
impl<'atblock> AnyExtrinsicInfo<'atblock> {
/// For a slice of extrinsics, return a vec of information about each one.
pub fn new(
bytes: &[Vec<u8>],
metadata: &'atblock RuntimeMetadata,
legacy_types: &'atblock TypeRegistrySet<'atblock>,
) -> Result<Vec<Self>, ExtrinsicsError> {
let infos = match metadata {
RuntimeMetadata::V8(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V9(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V10(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V11(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V12(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V13(m) => extrinsic_info_inner(bytes, m, legacy_types),
RuntimeMetadata::V14(m) => extrinsic_info_inner(bytes, m, &m.types),
RuntimeMetadata::V15(m) => extrinsic_info_inner(bytes, m, &m.types),
RuntimeMetadata::V16(m) => extrinsic_info_inner(bytes, m, &m.types),
unknown => {
return Err(ExtrinsicsError::UnsupportedMetadataVersion {
version: unknown.version(),
});
}
}?;
fn extrinsic_info_inner<'atblock, Info, Resolver>(
bytes: &[Vec<u8>],
args_info: &'atblock Info,
type_resolver: &'atblock Resolver,
) -> Result<Vec<AnyExtrinsicInfo<'atblock>>, ExtrinsicsError>
where
Info: frame_decode::extrinsics::ExtrinsicTypeInfo,
Info::TypeId: Clone + core::fmt::Display + core::fmt::Debug + Send + Sync + 'static,
Resolver: scale_type_resolver::TypeResolver<TypeId = Info::TypeId>,
AnyExtrinsicInfo<'atblock>: From<ExtrinsicInfo<'atblock, Info::TypeId, Resolver>>,
{
bytes
.iter()
.enumerate()
.map(|(index, bytes)| {
let cursor = &mut &**bytes;
let extrinsic_info = frame_decode::extrinsics::decode_extrinsic(
cursor,
args_info,
type_resolver,
)
.map_err(|reason| ExtrinsicsError::DecodeError { index, reason })?;
if !cursor.is_empty() {
return Err(ExtrinsicsError::LeftoverBytes {
index,
leftover_bytes: cursor.to_vec(),
});
}
Ok(ExtrinsicInfo {
info: extrinsic_info,
resolver: type_resolver,
}
.into())
})
.collect()
}
Ok(infos)
}
}
impl<'atblock> From<ExtrinsicInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyExtrinsicInfo<'atblock>
{
fn from(info: ExtrinsicInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>) -> Self {
AnyExtrinsicInfo::Legacy(info)
}
}
impl<'atblock> From<ExtrinsicInfo<'atblock, u32, scale_info::PortableRegistry>>
for AnyExtrinsicInfo<'atblock>
{
fn from(info: ExtrinsicInfo<'atblock, u32, scale_info::PortableRegistry>) -> Self {
AnyExtrinsicInfo::Current(info)
}
}
// Extrinsic information for a specific type ID and resolver type.
pub struct ExtrinsicInfo<'atblock, TypeId, Resolver> {
pub info: frame_decode::extrinsics::Extrinsic<'atblock, TypeId>,
pub resolver: &'atblock Resolver,
}
macro_rules! with_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicInfo::Legacy($info) => $fn,
AnyExtrinsicInfo::Current($info) => $fn,
}
};
}
pub(crate) use with_info;
@@ -1,213 +0,0 @@
use super::extrinsic_info::AnyExtrinsicInfo;
use crate::error::ExtrinsicTransactionExtensionError;
use crate::utils::Either;
use frame_decode::helpers::scale_decode;
use scale_info_legacy::{LookupName, TypeRegistrySet};
// Extrinsic extensions information for modern or legacy extrinsics.
enum AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock> {
Legacy(ExtrinsicExtensionsInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicExtensionsInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>),
}
struct ExtrinsicExtensionsInfo<'extrinsics, 'atblock, TypeId, Resolver> {
info: &'extrinsics frame_decode::extrinsics::ExtrinsicExtensions<'atblock, TypeId>,
resolver: &'atblock Resolver,
}
/// This represents the transaction extensions of an extrinsic.
pub struct ExtrinsicTransactionParams<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: AnyExtrinsicExtensionsInfo<'extrinsics, 'atblock>,
}
macro_rules! with_extensions_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicExtensionsInfo::Legacy($info) => $fn,
AnyExtrinsicExtensionsInfo::Current($info) => $fn,
}
};
}
impl<'extrinsics, 'atblock> ExtrinsicTransactionParams<'extrinsics, 'atblock> {
pub(crate) fn new(
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
) -> Option<Self> {
match info {
AnyExtrinsicInfo::Current(info) => {
let extension_info = info.info.transaction_extension_payload()?;
Some(Self {
all_bytes,
info: AnyExtrinsicExtensionsInfo::Current(ExtrinsicExtensionsInfo {
info: extension_info,
resolver: info.resolver,
}),
})
}
AnyExtrinsicInfo::Legacy(info) => {
let extension_info = info.info.transaction_extension_payload()?;
Some(Self {
all_bytes,
info: AnyExtrinsicExtensionsInfo::Legacy(ExtrinsicExtensionsInfo {
info: extension_info,
resolver: info.resolver,
}),
})
}
}
}
/// Get the raw bytes for all of the transaction extensions.
pub fn bytes(&self) -> &'extrinsics [u8] {
with_extensions_info!(&self.info => &self.all_bytes[info.info.range()])
}
/// iterate over each of the transaction extensions in this extrinsic.
pub fn iter(
&self,
) -> impl Iterator<Item = ExtrinsicTransactionExtension<'extrinsics, 'atblock>> {
match &self.info {
AnyExtrinsicExtensionsInfo::Legacy(extension_info) => {
let iter = extension_info
.info
.iter()
.map(|s| ExtrinsicTransactionExtension {
bytes: &self.all_bytes[s.range()],
info: ExtrinsicExtensionInfo {
name: s.name(),
type_id: s.ty(),
resolver: extension_info.resolver,
}
.into(),
});
Either::A(iter)
}
AnyExtrinsicExtensionsInfo::Current(extension_info) => {
let iter = extension_info
.info
.iter()
.map(|s| ExtrinsicTransactionExtension {
bytes: &self.all_bytes[s.range()],
info: ExtrinsicExtensionInfo {
name: s.name(),
type_id: s.ty(),
resolver: extension_info.resolver,
}
.into(),
});
Either::B(iter)
}
}
}
/// 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_as<T: scale_decode::DecodeAsFields>(
&self,
) -> Result<T, ExtrinsicTransactionExtensionError> {
with_extensions_info!(&self.info => {
let cursor = &mut self.bytes();
let mut fields = &mut info.info.iter().map(|named_arg| {
scale_decode::Field::new(named_arg.ty().clone(), Some(named_arg.name()))
});
let decoded = T::decode_as_fields(cursor, &mut fields, info.resolver)
.map_err(|e| ExtrinsicTransactionExtensionError::AllDecodeError { reason: e })?;
if !cursor.is_empty() {
return Err(ExtrinsicTransactionExtensionError::AllLeftoverBytes {
leftover_bytes: cursor.to_vec(),
})
}
Ok(decoded)
})
}
}
// Extrinsic single extension information for modern or legacy extrinsics.
enum AnyExtrinsicExtensionInfo<'extrinsics, 'atblock> {
Legacy(ExtrinsicExtensionInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(ExtrinsicExtensionInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>),
}
impl<'extrinsics, 'atblock>
From<ExtrinsicExtensionInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyExtrinsicExtensionInfo<'extrinsics, 'atblock>
{
fn from(
info: ExtrinsicExtensionInfo<'extrinsics, 'atblock, LookupName, TypeRegistrySet<'atblock>>,
) -> Self {
AnyExtrinsicExtensionInfo::Legacy(info)
}
}
impl<'extrinsics, 'atblock>
From<ExtrinsicExtensionInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>>
for AnyExtrinsicExtensionInfo<'extrinsics, 'atblock>
{
fn from(
info: ExtrinsicExtensionInfo<'extrinsics, 'atblock, u32, scale_info::PortableRegistry>,
) -> Self {
AnyExtrinsicExtensionInfo::Current(info)
}
}
struct ExtrinsicExtensionInfo<'extrinsics, 'atblock, TypeId, Resolver> {
name: &'extrinsics str,
type_id: &'extrinsics TypeId,
resolver: &'atblock Resolver,
}
macro_rules! with_extension_info {
(&$self:ident.$info:ident => $fn:expr) => {
#[allow(clippy::clone_on_copy)]
match &$self.$info {
AnyExtrinsicExtensionInfo::Legacy($info) => $fn,
AnyExtrinsicExtensionInfo::Current($info) => $fn,
}
};
}
/// This represents a single transaction extension in an extrinsic.
pub struct ExtrinsicTransactionExtension<'extrinsics, 'atblock> {
bytes: &'extrinsics [u8],
info: AnyExtrinsicExtensionInfo<'extrinsics, 'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicTransactionExtension<'extrinsics, 'atblock> {
/// The bytes for this transaction extension.
pub fn bytes(&self) -> &'extrinsics [u8] {
self.bytes
}
/// The name/identifier for this transaction extension.
pub fn name(&self) -> &'extrinsics str {
with_extension_info!(&self.info => info.name)
}
/// Decode the bytes for this transaction extension into a type that implements `scale_decode::DecodeAsType`.
pub fn decode_as<T: scale_decode::DecodeAsType>(
&self,
) -> Result<T, ExtrinsicTransactionExtensionError> {
with_extension_info!(&self.info => {
let cursor = &mut &*self.bytes;
let decoded = T::decode_as_type(cursor, info.type_id.clone(), info.resolver)
.map_err(|reason| ExtrinsicTransactionExtensionError::DecodeError {
name: info.name.to_string(),
reason
})?;
if !cursor.is_empty() {
return Err(ExtrinsicTransactionExtensionError::LeftoverBytes {
name: info.name.to_string(),
leftover_bytes: cursor.to_vec(),
});
}
Ok(decoded)
})
}
}
-112
View File
@@ -1,112 +0,0 @@
use super::extrinsic_call::ExtrinsicCall;
use super::extrinsic_info::{AnyExtrinsicInfo, with_info};
use super::extrinsic_transaction_extensions::ExtrinsicTransactionParams;
use crate::client::OfflineClientAtBlockT;
use crate::config::Config;
use crate::error::ExtrinsicsError;
/// This represents some extrinsics in a block, and carries everything that we need to decode information out of them.
pub struct Extrinsics<'atblock> {
bytes: Vec<Vec<u8>>,
// Each index in this vec should line up with one index in the above vec.
infos: Vec<AnyExtrinsicInfo<'atblock>>,
}
impl<'atblock> Extrinsics<'atblock> {
// In here we hide the messy logic needed to decode extrinsics into a consistent output given either current or legacy metadata.
pub(crate) fn new<'client: 'atblock, T, Client>(
bytes: Vec<Vec<u8>>,
client: &'atblock Client,
) -> Result<Self, ExtrinsicsError>
where
T: Config + 'client,
Client: OfflineClientAtBlockT<'client, T>,
{
let infos = AnyExtrinsicInfo::new(&bytes, client.metadata(), client.legacy_types())?;
Ok(Extrinsics { bytes, infos })
}
pub(crate) fn empty() -> Self {
Self {
bytes: vec![],
infos: vec![],
}
}
/// How many extrinsics are in this block?
pub fn len(&self) -> usize {
self.bytes.len()
}
/// Are there any extrinsics in this block?
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
/// Iterate over the extrinsics.
pub fn iter(&self) -> impl Iterator<Item = Extrinsic<'_, 'atblock>> {
self.bytes
.iter()
.zip(self.infos.iter())
.enumerate()
.map(|(idx, (bytes, info))| Extrinsic { idx, bytes, info })
}
}
/// This represents an extrinsic, and carries everything that we need to decode information out of it.
pub struct Extrinsic<'extrinsics, 'atblock> {
idx: usize,
bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
}
impl<'extrinsics, 'atblock> Extrinsic<'extrinsics, 'atblock> {
/// Get the index of this extrinsic in the block.
pub fn index(&self) -> usize {
self.idx
}
/// Get the raw bytes of this extrinsic.
pub fn bytes(&self) -> &'extrinsics [u8] {
self.bytes
}
/// Is this extrinsic signed?
pub fn is_signed(&self) -> bool {
with_info!(&self.info => info.info.is_signed())
}
/// Return information about the call that this extrinsic is making.
pub fn call(&self) -> ExtrinsicCall<'extrinsics, 'atblock> {
ExtrinsicCall::new(self.bytes, self.info)
}
/// Return only the bytes of the address that signed this extrinsic.
///
/// # Note
///
/// Returns `None` if the extrinsic is not signed.
pub fn address_bytes(&self) -> Option<&'extrinsics [u8]> {
with_info!(&self.info => {
info.info
.signature_payload()
.map(|s| &self.bytes[s.address_range()])
})
}
/// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
pub fn signature_bytes(&self) -> Option<&'extrinsics [u8]> {
with_info!(&self.info => {
info.info
.signature_payload()
.map(|s| &self.bytes[s.signature_range()])
})
}
/// Get information about the transaction extensions of this extrinsic.
pub fn transaction_extensions(
&self,
) -> Option<ExtrinsicTransactionParams<'extrinsics, 'atblock>> {
ExtrinsicTransactionParams::new(self.bytes, self.info)
}
}
-27
View File
@@ -1,27 +0,0 @@
//! `subxt-historic` is a library for working with non head-of-chain data on Substrate-based blockchains.
// TODO: Remove this when we're ready to release, and document everything!
#![allow(missing_docs)]
mod utils;
pub mod client;
pub mod config;
pub mod error;
pub mod extrinsics;
pub mod storage;
pub use client::{OfflineClient, OnlineClient};
pub use config::polkadot::PolkadotConfig;
pub use config::substrate::SubstrateConfig;
pub use error::Error;
/// External types and crates that may be useful.
pub mod ext {
pub use futures::stream::{Stream, StreamExt};
}
/// Helper types that could be useful.
pub mod helpers {
pub use crate::utils::{AnyResolver, AnyResolverError, AnyTypeId};
}
-350
View File
@@ -1,350 +0,0 @@
mod list_storage_entries_any;
mod storage_entry;
mod storage_info;
mod storage_key;
mod storage_value;
use crate::client::{OfflineClientAtBlockT, OnlineClientAtBlockT};
use crate::config::Config;
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::{EncodableValues, IntoEncodableValues};
/// Work with storage.
pub struct StorageClient<'atblock, Client, T> {
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageClient<'atblock, Client, T> {
/// Work with storage.
pub(crate) fn new(client: &'atblock Client) -> Self {
Self {
client,
marker: std::marker::PhantomData,
}
}
}
// Things that we can do offline with storage.
impl<'atblock, Client, T> StorageClient<'atblock, Client, T>
where
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>,
entry_name: impl Into<String>,
) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
let pallet_name = pallet_name.into();
let entry_name = entry_name.into();
let storage_info = AnyStorageInfo::new(
&pallet_name,
&entry_name,
self.client.metadata(),
self.client.legacy_types(),
)?;
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
/// storage entries like `:code` which are not listed in the metadata.
pub fn entries(&self) -> impl Iterator<Item = StorageEntriesItem<'atblock, Client, T>> {
let client = self.client;
let metadata = client.metadata();
let mut pallet_name = Cow::Borrowed("");
list_storage_entries_any::list_storage_entries_any(metadata).filter_map(move |entry| {
match entry {
frame_decode::storage::StorageEntry::In(name) => {
// Set the pallet name for upcoming entries:
pallet_name = name;
None
}
frame_decode::storage::StorageEntry::Name(entry_name) => {
// Output each entry with the last seen pallet name:
Some(StorageEntriesItem {
pallet_name: pallet_name.clone(),
entry_name,
client: self.client,
marker: std::marker::PhantomData,
})
}
}
})
}
}
/// Working with a specific storage entry.
pub struct StorageEntriesItem<'atblock, Client, T> {
pallet_name: Cow<'atblock, str>,
entry_name: Cow<'atblock, str>,
client: &'atblock Client,
marker: std::marker::PhantomData<T>,
}
impl<'atblock, Client, T> StorageEntriesItem<'atblock, Client, T>
where
T: Config + 'atblock,
Client: OfflineClientAtBlockT<'atblock, T>,
{
/// The pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}
/// The storage entry name.
pub fn entry_name(&self) -> &str {
&self.entry_name
}
/// Extract the relevant storage information so that we can work with this entry.
pub fn entry(&self) -> Result<StorageEntryClient<'atblock, Client, T>, StorageError> {
StorageClient {
client: self.client,
marker: std::marker::PhantomData,
}
.entry(&*self.pallet_name, &*self.entry_name)
}
}
/// 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 {
&self.pallet_name
}
/// Get the storage entry name.
pub fn entry_name(&self) -> &str {
&self.entry_name
}
/// 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 entry_name = &*self.entry_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_value(&self) -> Option<StorageValue<'atblock>> {
with_info!(info = &*self.info => {
info.info.default_value.as_ref().map(|default_value| {
StorageValue::new(self.info.clone(), default_value.clone())
})
})
}
}
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. 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 => {
info.info.keys.len()
});
// 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)?;
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());
Ok(value)
}
/// Iterate over the values underneath the provided keys.
pub async fn iter<Keys: IntoEncodableValues>(
&self,
keys: Keys,
) -> Result<
impl futures::Stream<Item = Result<StorageEntry<'atblock>, StorageError>>
+ Unpin
+ use<'atblock, Client, T, Keys>,
StorageError,
> {
use futures::stream::StreamExt;
use subxt_rpcs::methods::chain_head::{
ArchiveStorageEvent, ArchiveStorageQuery, 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)?;
let items = std::iter::once(ArchiveStorageQuery {
key: &*key_bytes,
query_type: StorageQueryType::DescendantsValues,
pagination_start_key: None,
});
let sub = self
.client
.rpc_methods()
.archive_v1_storage(block_hash.into(), items, None)
.await
.map_err(|e| 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(info, item.key.0, Cow::Owned(value.0))))
}
});
Ok(Box::pin(sub))
}
// Encode a storage key for this storage entry to bytes. The key can be a partial key
// (i.e there are still multiple values below it) or a complete key that points to a specific value.
//
// 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: 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.entry_name,
keys,
&info.info,
info.resolver,
).map_err(|e| StorageError::KeyEncodeError { reason: e })?;
Ok(key_bytes)
})
}
}
// Fetch a single storage value by its key.
async fn fetch<'atblock, Client, T>(
client: &Client,
key_bytes: &[u8],
) -> Result<Option<Vec<u8>>, StorageError>
where
T: Config + 'atblock,
Client: OnlineClientAtBlockT<'atblock, T>,
{
use subxt_rpcs::methods::chain_head::{
ArchiveStorageEvent, ArchiveStorageQuery, StorageQueryType,
};
let query = ArchiveStorageQuery {
key: key_bytes,
query_type: StorageQueryType::Value,
pagination_start_key: None,
};
let mut response_stream = client
.rpc_methods()
.archive_v1_storage(client.block_hash().into(), std::iter::once(query), None)
.await
.map_err(|e| StorageError::RpcError { reason: e })?;
let value = response_stream
.next()
.await
.transpose()
.map_err(|e| StorageError::RpcError { reason: e })?;
// No value found.
let Some(value) = value else {
return Ok(None);
};
let item = match value {
ArchiveStorageEvent::Item(item) => item,
// if it errors, return the error:
ArchiveStorageEvent::Error(err) => {
return Err(StorageError::StorageEventError { reason: err.error });
}
// if it's done, it means no value was returned:
ArchiveStorageEvent::Done => return Ok(None),
};
// This shouldn't happen, but if it does, the value we wanted wasn't found.
if item.key.0 != key_bytes {
return Ok(None);
}
// The bytes for the storage value. If this is None, then the API is misbehaving,
// ot no matching value was found.
let Some(value_bytes) = item.value else {
return Ok(None);
};
Ok(Some(value_bytes.0))
}
@@ -1,35 +0,0 @@
use frame_decode::storage::StorageEntryInfo;
use frame_metadata::RuntimeMetadata;
pub use frame_decode::storage::StorageEntry;
/// Returns an iterator listing the available storage entries in some metadata.
///
/// This basically calls [`StorageEntryInfo::storage_entries()`] for each metadata version,
/// returning an empty iterator where applicable (ie when passing legacy metadata and the
/// `legacy` features flag is not enabled).
pub fn list_storage_entries_any(
metadata: &RuntimeMetadata,
) -> impl Iterator<Item = StorageEntry<'_>> {
match metadata {
RuntimeMetadata::V0(_deprecated_metadata)
| RuntimeMetadata::V1(_deprecated_metadata)
| RuntimeMetadata::V2(_deprecated_metadata)
| RuntimeMetadata::V3(_deprecated_metadata)
| RuntimeMetadata::V4(_deprecated_metadata)
| RuntimeMetadata::V5(_deprecated_metadata)
| RuntimeMetadata::V6(_deprecated_metadata)
| RuntimeMetadata::V7(_deprecated_metadata) => {
Box::new(core::iter::empty()) as Box<dyn Iterator<Item = StorageEntry<'_>>>
}
RuntimeMetadata::V8(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V9(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V10(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V11(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V12(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V13(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V14(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V15(m) => Box::new(m.storage_entries()),
RuntimeMetadata::V16(m) => Box::new(m.storage_entries()),
}
}
-48
View File
@@ -1,48 +0,0 @@
use super::storage_info::AnyStorageInfo;
use super::storage_key::StorageKey;
use super::storage_value::StorageValue;
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<'atblock> {
key: Vec<u8>,
// This contains the storage information already:
value: StorageValue<'atblock>,
}
impl<'atblock> StorageEntry<'atblock> {
/// Create a new storage entry.
pub fn new(
info: Arc<AnyStorageInfo<'atblock>>,
key: Vec<u8>,
value: Cow<'atblock, [u8]>,
) -> Self {
Self {
key,
value: StorageValue::new(info, value),
}
}
/// Get the raw bytes for this storage entry's key.
pub fn key_bytes(&self) -> &[u8] {
&self.key
}
/// 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())
}
/// 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<'_, 'atblock>, StorageKeyError> {
StorageKey::new(&self.value.info, &self.key)
}
/// Return the storage value.
pub fn value(&self) -> &StorageValue<'atblock> {
&self.value
}
}
-102
View File
@@ -1,102 +0,0 @@
use crate::error::StorageError;
use frame_decode::storage::StorageTypeInfo;
use frame_metadata::RuntimeMetadata;
use scale_info_legacy::{LookupName, TypeRegistrySet};
pub enum AnyStorageInfo<'atblock> {
Legacy(StorageInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(StorageInfo<'atblock, u32, scale_info::PortableRegistry>),
}
impl<'atblock> AnyStorageInfo<'atblock> {
/// For a slice of storage entries, return a vec of information about each one.
pub fn new(
pallet_name: &str,
entry_name: &str,
metadata: &'atblock RuntimeMetadata,
legacy_types: &'atblock TypeRegistrySet<'atblock>,
) -> Result<Self, StorageError> {
let info = match metadata {
RuntimeMetadata::V8(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V9(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V10(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V11(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V12(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V13(m) => storage_info_inner(pallet_name, entry_name, m, legacy_types),
RuntimeMetadata::V14(m) => storage_info_inner(pallet_name, entry_name, m, &m.types),
RuntimeMetadata::V15(m) => storage_info_inner(pallet_name, entry_name, m, &m.types),
RuntimeMetadata::V16(m) => storage_info_inner(pallet_name, entry_name, m, &m.types),
unknown => {
return Err(StorageError::UnsupportedMetadataVersion {
version: unknown.version(),
});
}
}?;
fn storage_info_inner<'atblock, Info, Resolver>(
pallet_name: &str,
entry_name: &str,
m: &'atblock Info,
type_resolver: &'atblock Resolver,
) -> Result<AnyStorageInfo<'atblock>, StorageError>
where
Info: StorageTypeInfo,
Resolver: scale_type_resolver::TypeResolver<TypeId = Info::TypeId>,
AnyStorageInfo<'atblock>: From<StorageInfo<'atblock, Info::TypeId, Resolver>>,
{
m.storage_info(pallet_name, entry_name)
.map(|frame_storage_info| {
let info = StorageInfo {
info: frame_storage_info,
resolver: type_resolver,
};
AnyStorageInfo::from(info)
})
.map_err(|e| StorageError::ExtractStorageInfoError {
reason: e.into_owned(),
})
}
Ok(info)
}
/// Is the storage entry a map (ie something we'd provide extra keys to access a value, or otherwise iterate over)?
pub fn is_map(&self) -> bool {
match self {
AnyStorageInfo::Legacy(info) => !info.info.keys.is_empty(),
AnyStorageInfo::Current(info) => !info.info.keys.is_empty(),
}
}
}
impl<'atblock> From<StorageInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyStorageInfo<'atblock>
{
fn from(info: StorageInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>) -> Self {
AnyStorageInfo::Legacy(info)
}
}
impl<'atblock> From<StorageInfo<'atblock, u32, scale_info::PortableRegistry>>
for AnyStorageInfo<'atblock>
{
fn from(info: StorageInfo<'atblock, u32, scale_info::PortableRegistry>) -> Self {
AnyStorageInfo::Current(info)
}
}
pub struct StorageInfo<'atblock, TypeId: Clone, Resolver> {
pub info: frame_decode::storage::StorageInfo<'atblock, TypeId>,
pub resolver: &'atblock Resolver,
}
macro_rules! with_info {
($info:ident = $original_info:expr => $fn:expr) => {{
#[allow(clippy::clone_on_copy)]
let info = match $original_info {
AnyStorageInfo::Legacy($info) => $fn,
AnyStorageInfo::Current($info) => $fn,
};
info
}};
}
pub(crate) use with_info;
-176
View File
@@ -1,176 +0,0 @@
use super::AnyStorageInfo;
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::{IntoDecodableValues, StorageHasher};
enum AnyStorageKeyInfo<'atblock> {
Legacy(StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>),
Current(StorageKeyInfo<'atblock, u32, scale_info::PortableRegistry>),
}
impl<'atblock> From<StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>>
for AnyStorageKeyInfo<'atblock>
{
fn from(info: StorageKeyInfo<'atblock, LookupName, TypeRegistrySet<'atblock>>) -> Self {
AnyStorageKeyInfo::Legacy(info)
}
}
impl<'atblock> From<StorageKeyInfo<'atblock, u32, scale_info::PortableRegistry>>
for AnyStorageKeyInfo<'atblock>
{
fn from(info: StorageKeyInfo<'atblock, u32, scale_info::PortableRegistry>) -> Self {
AnyStorageKeyInfo::Current(info)
}
}
struct StorageKeyInfo<'atblock, TypeId, Resolver> {
info: frame_decode::storage::StorageKey<TypeId>,
resolver: &'atblock Resolver,
}
macro_rules! with_key_info {
($info:ident = $original_info:expr => $fn:expr) => {{
#[allow(clippy::clone_on_copy)]
let info = match $original_info {
AnyStorageKeyInfo::Legacy($info) => $fn,
AnyStorageKeyInfo::Current($info) => $fn,
};
info
}};
}
/// This represents the different parts of a storage key.
pub struct StorageKey<'entry, 'atblock> {
info: AnyStorageKeyInfo<'atblock>,
bytes: &'entry [u8],
}
impl<'entry, 'atblock> StorageKey<'entry, 'atblock> {
pub(crate) fn new(
info: &AnyStorageInfo<'atblock>,
bytes: &'entry [u8],
) -> Result<Self, StorageKeyError> {
with_info!(info = info => {
let cursor = &mut &*bytes;
let storage_key_info = frame_decode::storage::decode_storage_key_with_info(
cursor,
&info.info,
info.resolver,
).map_err(|e| {
StorageKeyError::DecodeError { reason: e.map_type_id(|id| id.to_string()) }
})?;
if !cursor.is_empty() {
return Err(StorageKeyError::LeftoverBytes {
leftover_bytes: cursor.to_vec(),
});
}
Ok(StorageKey {
info: StorageKeyInfo {
info: storage_key_info,
resolver: info.resolver,
}.into(),
bytes,
})
})
}
/// 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>> {
let parts_len = with_key_info!(info = &self.info => info.info.parts().len());
(0..parts_len).map(move |index| StorageKeyPart {
index,
info: &self.info,
bytes: self.bytes,
})
}
/// 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<'_, 'entry, 'atblock>> {
if index < self.parts().len() {
Some(StorageKeyPart {
index,
info: &self.info,
bytes: self.bytes,
})
} else {
None
}
}
}
/// This represents a part of a storage key.
pub struct StorageKeyPart<'key, 'entry, 'atblock> {
index: usize,
info: &'key AnyStorageKeyInfo<'atblock>,
bytes: &'entry [u8],
}
impl<'key, 'entry, 'atblock> StorageKeyPart<'key, 'entry, 'atblock> {
/// Get the raw bytes for this part of the storage key.
pub fn bytes(&self) -> &'entry [u8] {
with_key_info!(info = &self.info => {
let part = &info.info[self.index];
let hash_range = part.hash_range();
let value_range = part
.value()
.map(|v| v.range())
.unwrap_or(std::ops::Range { start: hash_range.end, end: hash_range.end });
let combined_range = std::ops::Range {
start: hash_range.start,
end: value_range.end,
};
&self.bytes[combined_range]
})
}
/// Get the hasher that was used to construct this part of the storage key.
pub fn hasher(&self) -> StorageHasher {
with_key_info!(info = &self.info => info.info[self.index].hasher())
}
/// 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> {
with_key_info!(info = &self.info => {
let part_info = &info.info[self.index];
let Some(value_info) = part_info.value() else {
return Ok(None);
};
let value_bytes = &self.bytes[value_info.range()];
let value_ty = value_info.ty().clone();
let decoded_key_part = T::decode_as_type(
&mut &*value_bytes,
value_ty,
info.resolver,
).map_err(|e| StorageKeyError::DecodePartError { index: self.index, reason: e })?;
Ok(Some(decoded_key_part))
})
}
}
-79
View File
@@ -1,79 +0,0 @@
use super::storage_info::AnyStorageInfo;
use super::storage_info::with_info;
use crate::error::StorageValueError;
use crate::utils::{AnyResolver, AnyTypeId};
use scale_decode::DecodeAsType;
use std::borrow::Cow;
use std::sync::Arc;
/// This represents a storage value.
pub struct StorageValue<'atblock> {
pub(crate) info: Arc<AnyStorageInfo<'atblock>>,
bytes: Cow<'atblock, [u8]>,
resolver: AnyResolver<'atblock, 'atblock>,
}
impl<'atblock> StorageValue<'atblock> {
/// Create a new storage value.
pub(crate) fn new(info: Arc<AnyStorageInfo<'atblock>>, bytes: Cow<'atblock, [u8]>) -> Self {
let resolver = match &*info {
AnyStorageInfo::Current(info) => AnyResolver::A(info.resolver),
AnyStorageInfo::Legacy(info) => AnyResolver::B(info.resolver),
};
Self {
info,
bytes,
resolver,
}
}
/// 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()
}
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
/// version of [`StorageValue::decode_as`], as the visitor is able to preserve lifetimes
/// and has access to more type information than is available via [`StorageValue::decode_as`].
pub fn visit<
V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock, 'atblock>>,
>(
&self,
visitor: V,
) -> Result<V::Value<'_, '_>, V::Error> {
let type_id = match &*self.info {
AnyStorageInfo::Current(info) => AnyTypeId::A(info.info.value_id),
AnyStorageInfo::Legacy(info) => AnyTypeId::B(info.info.value_id.clone()),
};
let cursor = &mut self.bytes();
scale_decode::visitor::decode_with_visitor(cursor, type_id, &self.resolver, visitor)
}
/// Decode this storage value.
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(
cursor,
info.info.value_id.clone(),
info.resolver,
).map_err(|e| StorageValueError::DecodeError { reason: e })?;
if !cursor.is_empty() {
return Err(StorageValueError::LeftoverBytes {
leftover_bytes: cursor.to_vec(),
});
}
Ok(value)
})
}
}
-7
View File
@@ -1,7 +0,0 @@
mod any_resolver;
mod either;
mod range_map;
pub use any_resolver::{AnyResolver, AnyResolverError, AnyTypeId};
pub use either::Either;
pub use range_map::RangeMap;
-186
View File
@@ -1,186 +0,0 @@
use super::Either;
use scale_info_legacy::LookupName;
use scale_type_resolver::ResolvedTypeVisitor;
/// A type resolver which could either be for modern or historic resolving.
pub type AnyResolver<'a, 'b> =
Either<&'a scale_info::PortableRegistry, &'a scale_info_legacy::TypeRegistrySet<'b>>;
/// A type ID which is either a modern or historic ID.
pub type AnyTypeId = Either<u32, scale_info_legacy::LookupName>;
impl Default for AnyTypeId {
fn default() -> Self {
// Not a sensible default, but we don't need / can't provide a sensible one.
AnyTypeId::A(u32::MAX)
}
}
impl From<u32> for AnyTypeId {
fn from(value: u32) -> Self {
AnyTypeId::A(value)
}
}
impl From<LookupName> for AnyTypeId {
fn from(value: LookupName) -> Self {
AnyTypeId::B(value)
}
}
impl TryFrom<AnyTypeId> for u32 {
type Error = ();
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
match value {
AnyTypeId::A(v) => Ok(v),
AnyTypeId::B(_) => Err(()),
}
}
}
impl TryFrom<AnyTypeId> for LookupName {
type Error = ();
fn try_from(value: AnyTypeId) -> Result<Self, Self::Error> {
match value {
AnyTypeId::A(_) => Err(()),
AnyTypeId::B(v) => Ok(v),
}
}
}
/// A resolve error that comes from using [`AnyResolver`] to resolve some [`AnyTypeId`] into a type.
#[derive(Debug, thiserror::Error)]
pub enum AnyResolverError {
#[error("got a {got} type ID but expected a {expected} type ID")]
TypeIdMismatch {
got: &'static str,
expected: &'static str,
},
#[error("{0}")]
ScaleInfo(scale_type_resolver::portable_registry::Error),
#[error("{0}")]
ScaleInfoLegacy(scale_info_legacy::type_registry::TypeRegistryResolveError),
}
impl<'a, 'b> scale_type_resolver::TypeResolver for AnyResolver<'a, 'b> {
type TypeId = AnyTypeId;
type Error = AnyResolverError;
fn resolve_type<'this, V: ResolvedTypeVisitor<'this, TypeId = Self::TypeId>>(
&'this self,
type_id: Self::TypeId,
visitor: V,
) -> Result<V::Value, Self::Error> {
match (self, type_id) {
(Either::A(resolver), Either::A(id)) => resolver
.resolve_type(id, ModernVisitor(visitor))
.map_err(AnyResolverError::ScaleInfo),
(Either::B(resolver), Either::B(id)) => resolver
.resolve_type(id, LegacyVisitor(visitor))
.map_err(AnyResolverError::ScaleInfoLegacy),
(Either::A(_), Either::B(_)) => Err(AnyResolverError::TypeIdMismatch {
got: "LookupName",
expected: "u32",
}),
(Either::B(_), Either::A(_)) => Err(AnyResolverError::TypeIdMismatch {
got: "u32",
expected: "LookupName",
}),
}
}
}
// We need to have a visitor which understands only modern or legacy types, and can wrap the more generic visitor
// that must be provided to AnyResolver::resolve_type. This then allows us to visit historic _or_ modern types
// using the single visitor provided by the user.
struct LegacyVisitor<V>(V);
struct ModernVisitor<V>(V);
mod impls {
use super::{AnyTypeId, LegacyVisitor, LookupName, ModernVisitor};
use scale_type_resolver::*;
// An ugly implementation which maps from modern or legacy types into our AnyTypeId,
// to make LegacyVisitor and ModernVisitor valid visitors when wrapping a generic "any" visitor.
macro_rules! impl_visitor_mapper {
($struc:ident, $type_id_ty:ident, $variant:ident) => {
impl<'this, V> ResolvedTypeVisitor<'this> for $struc<V>
where
V: ResolvedTypeVisitor<'this, TypeId = AnyTypeId>,
{
type TypeId = $type_id_ty;
type Value = V::Value;
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
self.0.visit_unhandled(kind)
}
fn visit_array(self, type_id: Self::TypeId, len: usize) -> Self::Value {
self.0.visit_array(AnyTypeId::$variant(type_id), len)
}
fn visit_not_found(self) -> Self::Value {
self.0.visit_not_found()
}
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
where
Path: PathIter<'this>,
Fields: FieldIter<'this, Self::TypeId>,
{
self.0.visit_composite(
path,
fields.map(|field| Field {
name: field.name,
id: AnyTypeId::$variant(field.id),
}),
)
}
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
where
Path: PathIter<'this>,
Fields: FieldIter<'this, Self::TypeId>,
Var: VariantIter<'this, Fields>,
{
self.0.visit_variant(
path,
variants.map(|variant| Variant {
index: variant.index,
name: variant.name,
fields: variant.fields.map(|field| Field {
name: field.name,
id: AnyTypeId::$variant(field.id),
}),
}),
)
}
fn visit_sequence<Path>(self, path: Path, type_id: Self::TypeId) -> Self::Value
where
Path: PathIter<'this>,
{
self.0.visit_sequence(path, AnyTypeId::$variant(type_id))
}
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
where
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
{
self.0
.visit_tuple(type_ids.map(|id| AnyTypeId::$variant(id)))
}
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
self.0.visit_primitive(primitive)
}
fn visit_compact(self, type_id: Self::TypeId) -> Self::Value {
self.0.visit_compact(AnyTypeId::$variant(type_id))
}
fn visit_bit_sequence(
self,
store_format: BitsStoreFormat,
order_format: BitsOrderFormat,
) -> Self::Value {
self.0.visit_bit_sequence(store_format, order_format)
}
}
};
}
impl_visitor_mapper!(ModernVisitor, u32, A);
impl_visitor_mapper!(LegacyVisitor, LookupName, B);
}
-49
View File
@@ -1,49 +0,0 @@
macro_rules! either {
($name:ident( $fst:ident, $($variant:ident),* )) => {
#[derive(Clone, Copy, Debug)]
pub enum $name<$fst, $($variant),*> {
$fst($fst),
$($variant($variant),)*
}
impl<$fst, $($variant),*> Iterator for $name<$fst, $($variant),*>
where
$fst: Iterator,
$($variant: Iterator<Item = $fst::Item>,)*
{
type Item = $fst::Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
$name::$fst(inner) => inner.next(),
$( $name::$variant(inner) => inner.next(), )*
}
}
}
impl <$fst, $($variant),*> futures::stream::Stream for $name<$fst, $($variant),*>
where
$fst: futures::stream::Stream,
$($variant: futures::stream::Stream<Item = $fst::Item>,)*
{
type Item = $fst::Item;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
use std::pin::Pin;
// SAFETY: This is safe because we never move the inner value out of the Pin.
unsafe {
match self.get_unchecked_mut() {
$name::$fst(inner) => Pin::new_unchecked(inner).poll_next(cx),
$( $name::$variant(inner) => Pin::new_unchecked(inner).poll_next(cx), )*
}
}
}
}
}
}
either!(Either(A, B));
-154
View File
@@ -1,154 +0,0 @@
use std::fmt::Display;
/// A map that associates ranges of keys with values.
#[derive(Debug, Clone)]
pub struct RangeMap<K, V> {
// (range_start, range_ended, value). This is
// guaranteed to be sorted and have non-overlapping ranges.
mapping: Vec<(K, K, V)>,
}
impl<K: Clone + Copy + Display + PartialOrd + Ord, V> RangeMap<K, V> {
/// Build an empty [`RangeMap`] as a placeholder.
pub fn empty() -> Self {
RangeMap {
mapping: Vec::new(),
}
}
/// Build a [`RangeMap`].
pub fn builder() -> RangeMapBuilder<K, V> {
RangeMapBuilder {
mapping: Vec::new(),
}
}
/// Return the value whose key is within the range, or None if not found.
pub fn get(&self, key: K) -> Option<&V> {
let idx = self
.mapping
.binary_search_by_key(&key, |&(start, end, _)| {
if key >= start && key < end {
key
} else {
start
}
})
.ok()?;
self.mapping.get(idx).map(|(_, _, val)| val)
}
}
/// A builder for constructing a [`RangeMap`]. Use [``RangeMap::builder()`] to create one.
#[derive(Debug, Clone)]
pub struct RangeMapBuilder<K, V> {
mapping: Vec<(K, K, V)>,
}
impl<K: Clone + Copy + Display + PartialOrd + Ord, V> RangeMapBuilder<K, V> {
/// Try to add a range, mapping block numbers to a spec version.
///
/// Returns an error if the range is empty or overlaps with an existing range.
pub fn try_add_range(
&mut self,
start: K,
end: K,
val: V,
) -> Result<&mut Self, RangeMapError<K>> {
let (start, end) = if start < end {
(start, end)
} else {
(end, start)
};
if start == end {
return Err(RangeMapError::EmptyRange(start));
}
if let Some(&(s, e, _)) = self.mapping.iter().find(|&&(s, e, _)| start < e && end > s) {
return Err(RangeMapError::OverlappingRanges {
proposed: (start, end),
existing: (s, e),
});
}
self.mapping.push((start, end, val));
Ok(self)
}
/// Add a range of blocks with the given spec version.
///
/// # Panics
///
/// This method will panic if the range is empty or overlaps with an existing range.
pub fn add_range(mut self, start: K, end: K, val: V) -> Self {
if let Err(e) = self.try_add_range(start, end, val) {
panic!("{e}")
}
self
}
/// Finish adding ranges and build the [`RangeMap`].
pub fn build(mut self) -> RangeMap<K, V> {
self.mapping.sort_by_key(|&(start, _, _)| start);
RangeMap {
mapping: self.mapping,
}
}
}
/// An error that can occur when calling [`RangeMapBuilder::try_add_range()`].
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RangeMapError<K: Display> {
/// An error indicating that the proposed block range is empty.
#[error("Block range cannot be empty: start and end values must be different, but got {} for both", .0)]
EmptyRange(K),
/// An error indicating that the proposed block range overlaps with an existing one.
#[error("Overlapping block ranges are not allowed: proposed range is {}..{}, but we already have {}..{}", proposed.0, proposed.1, existing.0, existing.1)]
OverlappingRanges { proposed: (K, K), existing: (K, K) },
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_rangemap_get() {
let spec_version = RangeMap::builder()
.add_range(0, 100, 1)
.add_range(100, 200, 2)
.add_range(200, 300, 3)
.build();
assert_eq!(spec_version.get(0), Some(&1));
assert_eq!(spec_version.get(50), Some(&1));
assert_eq!(spec_version.get(100), Some(&2));
assert_eq!(spec_version.get(150), Some(&2));
assert_eq!(spec_version.get(200), Some(&3));
assert_eq!(spec_version.get(250), Some(&3));
assert_eq!(spec_version.get(300), None);
}
#[test]
fn test_rangemap_set() {
let mut spec_version = RangeMap::builder()
.add_range(0, 100, 1)
.add_range(200, 300, 3);
assert_eq!(
spec_version.try_add_range(99, 130, 2).unwrap_err(),
RangeMapError::OverlappingRanges {
proposed: (99, 130),
existing: (0, 100),
}
);
assert_eq!(
spec_version.try_add_range(170, 201, 2).unwrap_err(),
RangeMapError::OverlappingRanges {
proposed: (170, 201),
existing: (200, 300),
}
);
}
}
-1
View File
@@ -6,7 +6,6 @@
//! to Substrate based chains.
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(any(
all(feature = "web", feature = "native"),
-175
View File
@@ -1,175 +0,0 @@
[package]
name = "subxt-new"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
publish = true
license.workspace = true
readme = "../README.md"
repository.workspace = true
documentation.workspace = true
homepage.workspace = true
description = "Submit extrinsics (transactions) to a substrate node via RPC"
keywords = ["parity", "substrate", "blockchain"]
[lints]
workspace = true
[features]
# For dev and documentation reasons we enable more features than are often desired.
# it's recommended to use `--no-default-features` and then select what you need.
default = ["jsonrpsee", "native"]
# Features that we expect to be enabled for documentation.
docs = [
"default",
"unstable-light-client",
"runtime",
"reconnecting-rpc-client",
]
# Enable this for native (ie non web/wasm builds).
# Exactly 1 of "web" and "native" is expected.
native = [
"subxt-lightclient?/native",
"subxt-rpcs/native",
"tokio-util",
"tokio?/sync",
"sp-crypto-hashing/std",
]
# Enable this for web/wasm builds.
# Exactly 1 of "web" and "native" is expected.
web = [
"subxt-lightclient?/web",
"subxt-macro/web",
"subxt-rpcs/web",
"tokio?/sync",
]
# Feature flag to enable the default future executor.
# Technically it's a hack enable to both but simplifies the conditional compilation
# and subxt is selecting executor based on the used platform.
#
# For instance `wasm-bindgen-futures` panics if the platform isn't wasm32 and
# similar for tokio that requires a tokio runtime to be initialized.
runtime = ["tokio/rt", "wasm-bindgen-futures"]
# Enable this to use the reconnecting rpc client
reconnecting-rpc-client = ["subxt-rpcs/reconnecting-rpc-client"]
# Enable this to use jsonrpsee, which enables the jsonrpsee RPC client, and
# a couple of util functions which rely on jsonrpsee.
jsonrpsee = [
"dep:jsonrpsee",
"subxt-rpcs/jsonrpsee",
"runtime"
]
# Enable this to fetch and utilize the latest unstable metadata from a node.
# The unstable metadata is subject to breaking changes and the subxt might
# fail to decode the metadata properly. Use this to experiment with the
# latest features exposed by the metadata.
unstable-metadata = []
# Activate this to expose the Light Client functionality.
# Note that this feature is experimental and things may break or not work as expected.
unstable-light-client = ["subxt-lightclient", "subxt-rpcs/unstable-light-client"]
# Activate this to expose the ability to generate metadata from Wasm runtime files.
runtime-wasm-path = ["subxt-macro/runtime-wasm-path"]
[dependencies]
async-trait = { workspace = true }
base58 = { workspace = true }
blake2 = { workspace = true }
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
derive-where = { workspace = true }
scale-info = { workspace = true, features = ["default"] }
scale-info-legacy = { workspace = true }
scale-value = { workspace = true, features = ["default"] }
scale-bits = { workspace = true, features = ["default"] }
scale-decode = { workspace = true, features = ["default"] }
scale-encode = { workspace = true, features = ["default"] }
futures = { workspace = true }
hex = { workspace = true }
impl-serde = { workspace = true, default-features = false }
keccak-hash = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["default", "raw_value"] }
sp-crypto-hashing = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
frame-metadata = { workspace = true }
frame-decode = { workspace = true, features = ["legacy-types"] }
either = { workspace = true }
web-time = { workspace = true }
# Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256:
primitive-types = { workspace = true, features = ["codec", "scale-info", "serde"] }
# Included if the "jsonrpsee" feature is enabled.
jsonrpsee = { workspace = true, optional = true, features = ["jsonrpsee-types"] }
# Other subxt crates we depend on.
subxt-macro = { workspace = true }
subxt-metadata = { workspace = true, features = ["std", "legacy"] }
subxt-lightclient = { workspace = true, optional = true, default-features = false }
subxt-rpcs = { workspace = true }
# For parsing urls to disallow insecure schemes
url = { workspace = true }
# Included if "native" feature is enabled
tokio-util = { workspace = true, features = ["compat"], optional = true }
# Included if the reconnecting rpc client feature is enabled
# Only the `tokio/sync` is used in the reconnecting rpc client
# and that compiles both for native and web.
tokio = { workspace = true, optional = true }
wasm-bindgen-futures = { workspace = true, optional = true }
[dev-dependencies]
bitvec = { workspace = true }
codec = { workspace = true, features = ["derive", "bit-vec"] }
scale-info = { workspace = true, features = ["bit-vec"] }
tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread", "sync"] }
sp-core = { workspace = true, features = ["std"] }
sp-keyring = { workspace = true, features = ["std"] }
sp-runtime = { workspace = true, features = ["std"] }
assert_matches = { workspace = true }
subxt-signer = { path = "../signer", features = ["unstable-eth"] }
subxt-rpcs = { workspace = true, features = ["subxt", "mock-rpc-client"] }
# Tracing subscriber is useful for light-client examples to ensure that
# the `bootNodes` and chain spec are configured correctly. If all is fine, then
# the light-client will emit INFO logs with
# `GrandPa warp sync finished` and `Finalized block runtime ready.`
tracing-subscriber = { workspace = true }
# These deps are needed to test the reconnecting rpc client
jsonrpsee = { workspace = true, features = ["server"] }
tower = { workspace = true }
hyper = { workspace = true }
http-body = { workspace = true }
[[example]]
name = "light_client_basic"
path = "examples/light_client_basic.rs"
required-features = ["unstable-light-client", "jsonrpsee"]
[[example]]
name = "light_client_local_node"
path = "examples/light_client_local_node.rs"
required-features = ["unstable-light-client", "jsonrpsee", "native"]
[[example]]
name = "setup_reconnecting_rpc_client"
path = "examples/setup_reconnecting_rpc_client.rs"
required-features = ["reconnecting-rpc-client"]
[package.metadata.docs.rs]
features = ["docs"]
[package.metadata.playground]
features = ["default", "unstable-light-client"]
-43
View File
@@ -1,43 +0,0 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client that subscribes to blocks of the Polkadot network.
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");
// Decode each signed extrinsic in the block dynamically
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let Some(transaction_extensions) = ext.transaction_extensions() else {
continue; // we do not look at inherents in this example
};
// Decode the fields into our dynamic Value type to display:
let fields = ext.decode_as_fields::<scale_value::Value>()?;
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.
if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"]
.contains(&signed_ext.name())
{
println!(" {}: {}", signed_ext.name(), signed_ext.value()?);
}
}
println!(" Fields:");
println!(" {fields}\n");
}
}
Ok(())
}
-64
View File
@@ -1,64 +0,0 @@
#![allow(missing_docs)]
use subxt::{
OnlineClient, PolkadotConfig,
utils::{AccountId32, MultiAddress},
};
use codec::Decode;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
use polkadot::balances::calls::types::TransferKeepAlive;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client that subscribes to blocks of the Polkadot network.
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
// For each block, print details about the `TransferKeepAlive` transactions we are interested in.
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash}):");
let extrinsics = block.extrinsics().await?;
for transfer in extrinsics.find::<TransferKeepAlive>() {
let transfer = transfer?;
let Some(extensions) = transfer.details.transaction_extensions() else {
panic!("TransferKeepAlive should be signed")
};
let addr_bytes = transfer
.details
.address_bytes()
.expect("TransferKeepAlive should be signed");
let sender = MultiAddress::<AccountId32, ()>::decode(&mut &addr_bytes[..])
.expect("Decoding should work");
let sender = display_address(&sender);
let receiver = display_address(&transfer.value.dest);
let value = transfer.value.value;
let tip = extensions.tip().expect("Should have tip");
let nonce = extensions.nonce().expect("Should have nonce");
println!(
" Transfer of {value} DOT:\n {sender} (Tip: {tip}, Nonce: {nonce}) ---> {receiver}",
);
}
}
Ok(())
}
fn display_address(addr: &MultiAddress<AccountId32, ()>) -> String {
if let MultiAddress::Id(id32) = addr {
format!("{id32}")
} else {
"MultiAddress::...".into()
}
}
-63
View File
@@ -1,63 +0,0 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
// For each block, print a bunch of information about it:
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number}:");
println!(" Hash: {block_hash}");
println!(" Extrinsics:");
// Log each of the extrinsic with it's associated events:
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let idx = ext.index();
let events = ext.events().await?;
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));
// See the API docs for more ways to decode extrinsics:
let decoded_ext = ext.as_root_extrinsic::<polkadot::Call>();
println!(" Extrinsic #{idx}:");
println!(" Bytes: {bytes_hex}");
println!(" Decoded: {decoded_ext:?}");
println!(" Events:");
for evt in events.iter() {
let evt = evt?;
let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.decode_as_fields::<scale_value::Value>()?;
println!(" {pallet_name}_{event_name}");
println!(" {event_values}");
}
println!(" Transaction Extensions:");
if let Some(transaction_extensions) = ext.transaction_extensions() {
for transaction_extension in transaction_extensions.iter() {
let name = transaction_extension.name();
let value = transaction_extension.value()?.to_string();
println!(" {name}: {value}");
}
}
}
}
Ok(())
}
-26
View File
@@ -1,26 +0,0 @@
#![allow(missing_docs)]
use subxt::dynamic::Value;
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// 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)?;
// 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)?;
// 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(())
}
-24
View File
@@ -1,24 +0,0 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// A query to obtain some constant:
let constant_query = polkadot::constants().system().block_length();
// 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(())
}
-48
View File
@@ -1,48 +0,0 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Get events for the latest block:
let events = api.events().at_latest().await?;
// We can dynamically decode events:
println!("Dynamic event details:");
for event in events.iter() {
let event = event?;
let pallet = event.pallet_name();
let variant = event.variant_name();
let field_values = event.decode_as_fields::<scale_value::Value>()?;
println!("{pallet}::{variant}: {field_values}");
}
// Or we can attempt to statically decode them into the root Event type:
println!("Static event details:");
for event in events.iter() {
let event = event?;
if let Ok(ev) = event.as_root_event::<polkadot::Event>() {
println!("{ev:?}");
} else {
println!("<Cannot decode event>");
}
}
// Or we can look for specific events which match our statically defined ones:
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(ev) = transfer_event {
println!(" - Balance transfer success: value: {:?}", ev.amount);
} else {
println!(" - No balance transfer event found in this block");
}
Ok(())
}
-47
View File
@@ -1,47 +0,0 @@
#![allow(missing_docs)]
use futures::StreamExt;
use subxt::{PolkadotConfig, client::OnlineClient, lightclient::LightClient};
// 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 {}
const POLKADOT_SPEC: &str = include_str!("../../artifacts/demo_chain_specs/polkadot.json");
const ASSET_HUB_SPEC: &str =
include_str!("../../artifacts/demo_chain_specs/polkadot_asset_hub.json");
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The lightclient logs are informative:
tracing_subscriber::fmt::init();
// Instantiate a light client with the Polkadot relay chain,
// and connect it to Asset Hub, too.
let (lightclient, polkadot_rpc) = LightClient::relay_chain(POLKADOT_SPEC)?;
let asset_hub_rpc = lightclient.parachain(ASSET_HUB_SPEC)?;
// Create Subxt clients from these Smoldot backed RPC clients.
let polkadot_api = OnlineClient::<PolkadotConfig>::from_rpc_client(polkadot_rpc).await?;
let asset_hub_api = OnlineClient::<PolkadotConfig>::from_rpc_client(asset_hub_rpc).await?;
// Use them!
let polkadot_sub = polkadot_api
.blocks()
.subscribe_finalized()
.await?
.map(|block| ("Polkadot", block));
let parachain_sub = asset_hub_api
.blocks()
.subscribe_finalized()
.await?
.map(|block| ("AssetHub", block));
let mut stream_combinator = futures::stream::select(polkadot_sub, parachain_sub);
while let Some((chain, block)) = stream_combinator.next().await {
let block = block?;
println!(" Chain {:?} hash={:?}", chain, block.hash());
}
Ok(())
}
-58
View File
@@ -1,58 +0,0 @@
#![allow(missing_docs)]
use subxt::utils::fetch_chainspec_from_rpc_node;
use subxt::{
PolkadotConfig,
client::OnlineClient,
lightclient::{ChainConfig, LightClient},
};
use subxt_signer::sr25519::dev;
// 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]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The smoldot logs are informative:
tracing_subscriber::fmt::init();
// Use a utility function to obtain a chain spec from a locally running node:
let chain_spec = fetch_chainspec_from_rpc_node("ws://127.0.0.1:9944").await?;
// Configure the bootnodes of this chain spec. In this case, because we start one
// single node, the bootnodes must be overwritten for the light client to connect
// to the local node.
//
// The `12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp` is the P2P address
// from a local polkadot node starting with
// `--node-key 0000000000000000000000000000000000000000000000000000000000000001`
let chain_config = ChainConfig::chain_spec(chain_spec.get()).set_bootnodes([
"/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp",
])?;
// Start the light client up, establishing a connection to the local node.
let (_light_client, chain_rpc) = LightClient::relay_chain(chain_config)?;
let api = OnlineClient::<PolkadotConfig>::from_rpc_client(chain_rpc).await?;
// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
// Submit the balance transfer extrinsic from Alice, and wait for it to be successful
// and in a finalized block. We get back the extrinsic events if all is well.
let from = dev::alice();
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;
// Find a Transfer event and print it.
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}
Ok(())
}
-61
View File
@@ -1,61 +0,0 @@
#![allow(missing_docs)]
use subxt::backend::{legacy::LegacyRpcMethods, rpc::RpcClient};
use subxt::config::DefaultExtrinsicParamsBuilder as Params;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// First, create a raw RPC client:
let rpc_client = RpcClient::from_url("ws://127.0.0.1:9944").await?;
// Use this to construct our RPC methods:
let rpc = LegacyRpcMethods::<PolkadotConfig>::new(rpc_client.clone());
// We can use the same client to drive our full Subxt interface too:
let api = OnlineClient::<PolkadotConfig>::from_rpc_client(rpc_client.clone()).await?;
// Now, we can make some RPC calls using some legacy RPC methods.
println!(
"📛 System Name: {:?}\n🩺 Health: {:?}\n🖫 Properties: {:?}\n🔗 Chain: {:?}\n",
rpc.system_name().await?,
rpc.system_health().await?,
rpc.system_properties().await?,
rpc.system_chain().await?
);
// We can also interleave RPC calls and using the full Subxt client, here to submit multiple
// transactions using the legacy `system_account_next_index` RPC call, which returns a nonce
// that is adjusted for any transactions already in the pool:
let alice = dev::alice();
let bob = dev::bob();
loop {
let current_nonce = rpc
.system_account_next_index(&alice.public_key().into())
.await?;
let ext_params = Params::new().mortal(8).nonce(current_nonce).build();
let balance_transfer = polkadot::tx()
.balances()
.transfer_allow_death(bob.public_key().into(), 1_000_000);
let ext_hash = api
.tx()
.create_partial_offline(&balance_transfer, ext_params)?
.sign(&alice)
.submit()
.await?;
println!("Submitted ext {ext_hash} with nonce {current_nonce}");
// Sleep less than block time, but long enough to ensure
// not all transactions end up in the same block.
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
-30
View File
@@ -1,30 +0,0 @@
#![allow(missing_docs)]
use subxt::utils::AccountId32;
use subxt::{OnlineClient, config::PolkadotConfig};
use subxt_signer::sr25519::dev;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// 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
.runtime_api()
.at_latest()
.await?
.call(runtime_api_call)
.await?;
println!("Account nonce: {:#?}", nonce);
Ok(())
}
-23
View File
@@ -1,23 +0,0 @@
#![allow(missing_docs)]
use subxt::ext::codec::{Compact, Decode};
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Use runtime APIs at the latest block:
let runtime_apis = api.runtime_api().at_latest().await?;
// Ask for metadata and decode it:
let result_bytes = runtime_apis.call_raw("Metadata_metadata", None).await?;
let (_, meta): (Compact<u32>, RuntimeMetadataPrefixed) = Decode::decode(&mut &*result_bytes)?;
println!("{meta:?}");
Ok(())
}
-28
View File
@@ -1,28 +0,0 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, config::PolkadotConfig};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a runtime API payload that calls into
// `AccountNonceApi_account_nonce` function.
let account = dev::alice().public_key().into();
let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account);
// Submit the call and get back a result.
let nonce = api
.runtime_api()
.at_latest()
.await?
.call(runtime_api_call)
.await;
println!("AccountNonceApi_account_nonce for Alice: {nonce:?}");
Ok(())
}
-86
View File
@@ -1,86 +0,0 @@
#![allow(missing_docs)]
use std::{
fmt::Write,
pin::Pin,
sync::{Arc, Mutex},
};
use subxt::{
OnlineClient, PolkadotConfig,
backend::rpc::{RawRpcFuture, RawRpcSubscription, RawValue, RpcClient, RpcClientT},
};
// A dummy RPC client that doesn't actually handle requests properly
// at all, but instead just logs what requests to it were made.
struct MyLoggingClient {
log: Arc<Mutex<String>>,
}
// We have to implement this fairly low level trait to turn [`MyLoggingClient`]
// into an RPC client that we can make use of in Subxt. Here we just log the requests
// made but don't forward them to any real node, and instead just return nonsense.
impl RpcClientT for MyLoggingClient {
fn request_raw<'a>(
&'a self,
method: &'a str,
params: Option<Box<RawValue>>,
) -> RawRpcFuture<'a, Box<RawValue>> {
writeln!(
self.log.lock().unwrap(),
"{method}({})",
params.as_ref().map(|p| p.get()).unwrap_or("[]")
)
.unwrap();
// We've logged the request; just return garbage. Because a boxed future is returned,
// you're able to run whatever async code you'd need to actually talk to a node.
let res = RawValue::from_string("[]".to_string()).unwrap();
Box::pin(std::future::ready(Ok(res)))
}
fn subscribe_raw<'a>(
&'a self,
sub: &'a str,
params: Option<Box<RawValue>>,
unsub: &'a str,
) -> RawRpcFuture<'a, RawRpcSubscription> {
writeln!(
self.log.lock().unwrap(),
"{sub}({}) (unsub: {unsub})",
params.as_ref().map(|p| p.get()).unwrap_or("[]")
)
.unwrap();
// We've logged the request; just return garbage. Because a boxed future is returned,
// and that will return a boxed Stream impl, you have a bunch of flexibility to build
// and return whatever type of Stream you see fit.
let res = RawValue::from_string("[]".to_string()).unwrap();
let stream = futures::stream::once(async move { Ok(res) });
let stream: Pin<Box<dyn futures::Stream<Item = _> + Send>> = Box::pin(stream);
// This subscription does not provide an ID.
Box::pin(std::future::ready(Ok(RawRpcSubscription {
stream,
id: None,
})))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Instantiate our replacement RPC client.
let log = Arc::default();
let rpc_client = {
let inner = MyLoggingClient {
log: Arc::clone(&log),
};
RpcClient::new(inner)
};
// Pass this into our OnlineClient to instantiate it. This will lead to some
// RPC calls being made to fetch chain details/metadata, which will immediately
// fail..
let _ = OnlineClient::<PolkadotConfig>::from_rpc_client(rpc_client).await;
// But, we can see that the calls were made via our custom RPC client:
println!("Log of calls made:\n\n{}", log.lock().unwrap().as_str());
Ok(())
}
-35
View File
@@ -1,35 +0,0 @@
#![allow(missing_docs)]
use subxt::ext::codec::Decode;
use subxt::metadata::Metadata;
use subxt::utils::H256;
use subxt::{OfflineClient, config::PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// We need to obtain the following details for an OfflineClient to be instantiated:
// 1. Genesis hash (RPC call: chain_getBlockHash(0)):
let genesis_hash = {
let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
let bytes = hex::decode(h).unwrap();
H256::from_slice(&bytes)
};
// 2. A runtime version (system_version constant on a Substrate node has these):
let runtime_version = subxt::client::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
};
// 3. Metadata (I'll load it from the downloaded metadata, but you can use
// `subxt metadata > file.scale` to download it):
let metadata = {
let bytes = std::fs::read("./artifacts/polkadot_metadata_small.scale").unwrap();
Metadata::decode(&mut &*bytes).unwrap()
};
// Create an offline client using the details obtained above:
let _api = OfflineClient::<PolkadotConfig>::new(genesis_hash, runtime_version, metadata);
Ok(())
}
-54
View File
@@ -1,54 +0,0 @@
#![allow(missing_docs)]
use subxt::config::{
Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, PolkadotConfig, SubstrateConfig,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
derive_for_type(
path = "staging_xcm::v3::multilocation::MultiLocation",
derive = "Clone, codec::Encode",
recursive
)
)]
pub mod runtime {}
use runtime::runtime_types::staging_xcm::v3::multilocation::MultiLocation;
use runtime::runtime_types::xcm::v3::junctions::Junctions;
// We don't need to construct this at runtime, so an empty enum is appropriate.
pub enum AssetHubConfig {}
impl Config for AssetHubConfig {
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <PolkadotConfig as Config>::Address;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type ExtrinsicParams = DefaultExtrinsicParams<AssetHubConfig>;
// Here we use the MultiLocation from the metadata as a part of the config:
// The `ChargeAssetTxPayment` signed extension that is part of the ExtrinsicParams above, now uses the type:
type AssetId = MultiLocation;
}
#[tokio::main]
async fn main() {
// With the config defined, we can create an extrinsic with subxt:
let client = subxt::OnlineClient::<AssetHubConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Build extrinsic params using an asset at this location as a tip:
let location: MultiLocation = MultiLocation {
parents: 3,
interior: Junctions::Here,
};
let tx_config = DefaultExtrinsicParamsBuilder::<AssetHubConfig>::new()
.tip_of(1234, location)
.build();
// And provide the extrinsic params including the tip when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config)
.await;
}
-97
View File
@@ -1,97 +0,0 @@
#![allow(missing_docs)]
use codec::Encode;
use subxt::client::ClientState;
use subxt::config::{
Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError, HashFor,
transaction_extensions::Params,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
pub mod runtime {}
// We don't need to construct this at runtime,
// so an empty enum is appropriate:
pub enum CustomConfig {}
impl Config for CustomConfig {
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = CustomExtrinsicParams<Self>;
type AssetId = u32;
}
// This represents some arbitrary (and nonsensical) custom parameters that
// will be attached to transaction extra and additional payloads:
pub struct CustomExtrinsicParams<T: Config> {
genesis_hash: HashFor<T>,
tip: u128,
foo: bool,
}
// We can provide a "pretty" interface to allow users to provide these:
#[derive(Default)]
pub struct CustomExtrinsicParamsBuilder {
tip: u128,
foo: bool,
}
impl CustomExtrinsicParamsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn tip(mut self, value: u128) -> Self {
self.tip = value;
self
}
pub fn enable_foo(mut self) -> Self {
self.foo = true;
self
}
}
impl<T: Config> Params<T> for CustomExtrinsicParamsBuilder {}
// Describe how to fetch and then encode the params:
impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
type Params = CustomExtrinsicParamsBuilder;
// Gather together all of the params we will need to encode:
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(Self {
genesis_hash: client.genesis_hash,
tip: params.tip,
foo: params.foo,
})
}
}
// Encode the relevant params when asked:
impl<T: Config> ExtrinsicParamsEncoder for CustomExtrinsicParams<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
(self.tip, self.foo).encode_to(v);
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.genesis_hash.encode_to(v)
}
}
#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Build your custom "Params":
let tx_config = CustomExtrinsicParamsBuilder::new().tip(1234).enable_foo();
// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config)
.await;
}
@@ -1,106 +0,0 @@
#![allow(missing_docs)]
use codec::Encode;
use scale_encode::EncodeAsType;
use scale_info::PortableRegistry;
use subxt::client::ClientState;
use subxt::config::transaction_extensions;
use subxt::config::{
Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder,
ExtrinsicParamsError,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod runtime {}
// We don't need to construct this at runtime,
// so an empty enum is appropriate:
#[derive(EncodeAsType)]
pub enum CustomConfig {}
impl Config for CustomConfig {
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = transaction_extensions::AnyOf<
Self,
(
// Load in the existing signed extensions we're interested in
// (if the extension isn't actually needed it'll just be ignored):
transaction_extensions::VerifySignature<Self>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<Self>,
transaction_extensions::CheckMortality<Self>,
transaction_extensions::ChargeAssetTxPayment<Self>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
// And add a new one of our own:
CustomTransactionExtension,
),
>;
type AssetId = u32;
}
// Our custom signed extension doesn't do much:
pub struct CustomTransactionExtension;
// Give the extension a name; this allows `AnyOf` to look it
// up in the chain metadata in order to know when and if to use it.
impl<T: Config> transaction_extensions::TransactionExtension<T> for CustomTransactionExtension {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CustomTransactionExtension"
}
}
// Gather together any params we need for our signed extension, here none.
impl<T: Config> ExtrinsicParams<T> for CustomTransactionExtension {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CustomTransactionExtension)
}
}
// Encode whatever the extension needs to provide when asked:
impl ExtrinsicParamsEncoder for CustomTransactionExtension {
fn encode_value_to(&self, v: &mut Vec<u8>) {
"Hello".encode_to(v);
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
true.encode_to(v)
}
}
// When composing a tuple of signed extensions, the user parameters we need must
// be able to convert `Into` a tuple of corresponding `Params`. Here, we just
// "hijack" the default param builder, but add the `Params` (`()`) for our
// new signed extension at the end, to make the types line up. IN reality you may wish
// to construct an entirely new interface to provide the relevant `Params`.
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::Params {
let (a, b, c, d, e, f, g, h, i) = params.build();
(a, b, c, d, e, f, g, h, i, ())
}
#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Configure the tx params:
let tx_config = DefaultExtrinsicParamsBuilder::new().tip(1234);
// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config))
.await;
}
@@ -1,77 +0,0 @@
//! Example to utilize the `reconnecting rpc client` in subxt
//! which hidden behind behind `--feature reconnecting-rpc-client`
//!
//! To utilize full logs from the RPC client use:
//! `RUST_LOG="jsonrpsee=trace,subxt-reconnecting-rpc-client=trace"`
#![allow(missing_docs)]
use std::time::Duration;
use futures::StreamExt;
use subxt::backend::rpc::reconnecting_rpc_client::{ExponentialBackoff, RpcClient};
use subxt::{OnlineClient, PolkadotConfig};
// 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]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a new client with a reconnecting RPC client.
let rpc = RpcClient::builder()
// Reconnect with exponential backoff
//
// This API is "iterator-like" and we use `take` to limit the number of retries.
.retry_policy(
ExponentialBackoff::from_millis(100)
.max_delay(Duration::from_secs(10))
.take(3),
)
// There are other configurations as well that can be found at [`reconnecting_rpc_client::ClientBuilder`].
.build("ws://localhost:9944".to_string())
.await?;
// If you want to use the chainhead backend with the reconnecting RPC client, you can do so like this:
//
// ```
// use subxt::backend::chain_head:ChainHeadBackend;
// use subxt::OnlineClient;
//
// let backend = ChainHeadBackend::builder().build_with_background_task(RpcClient::new(rpc.clone()));
// let api: OnlineClient<PolkadotConfig> = OnlineClient::from_backend(Arc::new(backend)).await?;
// ```
let api: OnlineClient<PolkadotConfig> = OnlineClient::from_rpc_client(rpc.clone()).await?;
// Run for at most 100 blocks and print a bunch of information about it.
//
// The subscription is automatically re-started when the RPC client has reconnected.
// You can test that by stopping the polkadot node and restarting it.
let mut blocks_sub = api.blocks().subscribe_finalized().await?.take(100);
while let Some(block) = blocks_sub.next().await {
let block = match block {
Ok(b) => b,
Err(e) => {
// This can only happen on the legacy backend and the unstable backend
// will handle this internally.
if e.is_disconnected_will_reconnect() {
println!("The RPC connection was lost and we may have missed a few blocks");
continue;
}
return Err(e.into());
}
};
let block_number = block.number();
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");
}
Ok(())
}
@@ -1,35 +0,0 @@
//! Example to utilize the ChainHeadBackend rpc backend to subscribe to finalized blocks.
#![allow(missing_docs)]
use futures::StreamExt;
use subxt::backend::chain_head::{ChainHeadBackend, ChainHeadBackendBuilder};
use subxt::backend::rpc::RpcClient;
use subxt::{OnlineClient, PolkadotConfig};
// 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]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let rpc = RpcClient::from_url("ws://localhost:9944".to_string()).await?;
let backend: ChainHeadBackend<PolkadotConfig> =
ChainHeadBackendBuilder::default().build_with_background_driver(rpc.clone());
let api = OnlineClient::from_backend(std::sync::Arc::new(backend)).await?;
let mut blocks_sub = api.blocks().subscribe_finalized().await?.take(100);
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.number();
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");
}
Ok(())
}

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