mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-22 04:28:00 +00:00
Make subxt-core ready for publishing (#1508)
* Move Extrinsic decoding things to subxt_core and various tidy-ups * A couple more fixes and fmt * first pass moving tx logic to subxt_core * cargo fmt * fix wasm example * clippy * more clippy * WIP Adding examples and such * Move storage functionality more fully to subxt_core and nice examples for storage and txs * Add example for events * consistify how addresses/payloads are exposed in subxt-core and add runtime API fns * Add runtime API core example * fmt * remove scale-info patch * Add a little to the top level docs * swap args around * clippy * cargo fmt and fix wasm-example * doc fixes * no-std-ise new subxt-core additions * alloc, not core * more no-std fixes * A couple more fixes * Add back extrinsic decode test
This commit is contained in:
Generated
+258
-237
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -45,7 +45,7 @@ pub fn explore_calls(
|
||||
Usage:
|
||||
subxt explore pallet {pallet_name} calls <CALL>
|
||||
explore a specific call of this pallet
|
||||
|
||||
|
||||
{calls}
|
||||
"}
|
||||
};
|
||||
@@ -154,7 +154,10 @@ fn mocked_offline_client(metadata: Metadata) -> OfflineClient<SubstrateConfig> {
|
||||
H256::from_str("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3")
|
||||
.expect("Valid hash; qed");
|
||||
|
||||
let runtime_version = subxt::client::RuntimeVersion::new(9370, 20);
|
||||
let runtime_version = subxt::client::RuntimeVersion {
|
||||
spec_version: 9370,
|
||||
transaction_version: 20,
|
||||
};
|
||||
|
||||
OfflineClient::<SubstrateConfig>::new(genesis_hash, runtime_version, metadata)
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ pub fn generate_calls(
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #call_fn_args, )*
|
||||
) -> #crate_path::tx::Payload<types::#struct_name> {
|
||||
#crate_path::tx::Payload::new_static(
|
||||
) -> #crate_path::tx::payload::Payload<types::#struct_name> {
|
||||
#crate_path::tx::payload::Payload::new_static(
|
||||
#pallet_name,
|
||||
#call_name,
|
||||
types::#struct_name { #( #call_args, )* },
|
||||
|
||||
@@ -68,8 +68,8 @@ pub fn generate_constants(
|
||||
|
||||
Ok(quote! {
|
||||
#docs
|
||||
pub fn #fn_name(&self) -> #crate_path::constants::Address<#return_ty> {
|
||||
#crate_path::constants::Address::new_static(
|
||||
pub fn #fn_name(&self) -> #crate_path::constants::address::Address<#return_ty> {
|
||||
#crate_path::constants::address::Address::new_static(
|
||||
#pallet_name,
|
||||
#constant_name,
|
||||
[#(#constant_hash,)*]
|
||||
|
||||
@@ -70,8 +70,8 @@ fn generate_custom_value_fn(
|
||||
};
|
||||
|
||||
Some(quote!(
|
||||
pub fn #fn_name_ident(&self) -> #crate_path::custom_values::StaticAddress<#return_ty, #decodable> {
|
||||
#crate_path::custom_values::StaticAddress::new_static(#name, [#(#custom_value_hash,)*])
|
||||
pub fn #fn_name_ident(&self) -> #crate_path::custom_values::address::StaticAddress<#return_ty, #decodable> {
|
||||
#crate_path::custom_values::address::StaticAddress::new_static(#name, [#(#custom_value_hash,)*])
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ fn generate_runtime_api(
|
||||
|
||||
let method = quote!(
|
||||
#docs
|
||||
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, types::#method_name::output::Output> {
|
||||
#crate_path::runtime_api::Payload::new_static(
|
||||
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::payload::Payload<types::#struct_name, types::#method_name::output::Output> {
|
||||
#crate_path::runtime_api::payload::Payload::new_static(
|
||||
#trait_name_str,
|
||||
#method_name_str,
|
||||
types::#struct_name { #( #param_names, )* },
|
||||
|
||||
@@ -224,7 +224,16 @@ impl CodegenBuilder {
|
||||
}
|
||||
|
||||
/// Set the path to the `subxt` crate. By default, we expect it to be at `::subxt::ext::subxt_core`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the path provided is not an absolute path.
|
||||
pub fn set_subxt_crate_path(&mut self, crate_path: syn::Path) {
|
||||
if absolute_path(crate_path.clone()).is_err() {
|
||||
// Throw an error here, because otherwise we end up with a harder to comprehend error when
|
||||
// substitute types don't begin with an absolute path.
|
||||
panic!("The provided crate path must be an absolute path, ie prefixed with '::' or 'crate'");
|
||||
}
|
||||
self.crate_path = crate_path;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,11 +67,15 @@ sp-runtime = { workspace = true, optional = true }
|
||||
tracing = { workspace = true, default-features = false }
|
||||
|
||||
[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 }
|
||||
sp-runtime = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
// 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::signed_extensions::{
|
||||
ChargeAssetTxPayment, ChargeTransactionPayment, CheckNonce,
|
||||
};
|
||||
use crate::config::SignedExtension;
|
||||
use crate::dynamic::Value;
|
||||
use crate::{config::Config, error::Error, Metadata};
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
/// The signed extensions of an extrinsic.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExtrinsicSignedExtensions<'a, T: Config> {
|
||||
bytes: &'a [u8],
|
||||
metadata: &'a Metadata,
|
||||
_marker: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
|
||||
pub(crate) fn new(bytes: &'a [u8], metadata: &'a Metadata) -> Self {
|
||||
Self {
|
||||
bytes,
|
||||
metadata,
|
||||
_marker: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over each of the signed extension details of the extrinsic.
|
||||
/// If the decoding of any signed extension fails, an error item is yielded and the iterator stops.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<T>, Error>> {
|
||||
let signed_extension_types = self.metadata.extrinsic().signed_extensions();
|
||||
let num_signed_extensions = signed_extension_types.len();
|
||||
let bytes = self.bytes;
|
||||
let mut index = 0;
|
||||
let mut byte_start_idx = 0;
|
||||
let metadata = &self.metadata;
|
||||
|
||||
core::iter::from_fn(move || {
|
||||
if index == num_signed_extensions {
|
||||
return None;
|
||||
}
|
||||
|
||||
let extension = &signed_extension_types[index];
|
||||
let ty_id = extension.extra_ty();
|
||||
let cursor = &mut &bytes[byte_start_idx..];
|
||||
if let Err(err) = scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
&ty_id,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(|e| Error::Decode(e.into()))
|
||||
{
|
||||
index = num_signed_extensions; // (such that None is returned in next iteration)
|
||||
return Some(Err(err));
|
||||
}
|
||||
let byte_end_idx = bytes.len() - cursor.len();
|
||||
let bytes = &bytes[byte_start_idx..byte_end_idx];
|
||||
byte_start_idx = byte_end_idx;
|
||||
index += 1;
|
||||
Some(Ok(ExtrinsicSignedExtension {
|
||||
bytes,
|
||||
ty_id,
|
||||
identifier: extension.identifier(),
|
||||
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: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
|
||||
for ext in self.iter() {
|
||||
// If we encounter an error while iterating, we won't get any more results
|
||||
// back, so just return that error as we won't find the signed ext anyway.
|
||||
let ext = ext?;
|
||||
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 ExtrinsicSignedExtension<'a, T: Config> {
|
||||
bytes: &'a [u8],
|
||||
ty_id: u32,
|
||||
identifier: &'a str,
|
||||
metadata: &'a Metadata,
|
||||
_marker: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> ExtrinsicSignedExtension<'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>, Error> {
|
||||
let value = scale_value::scale::decode_as_type(
|
||||
&mut &self.bytes[..],
|
||||
&self.ty_id,
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
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: SignedExtension<T>>(&self) -> Result<Option<S::Decoded>, Error> {
|
||||
if !S::matches(self.identifier, self.ty_id, self.metadata.types()) {
|
||||
return Ok(None);
|
||||
}
|
||||
self.as_type::<S::Decoded>().map(Some)
|
||||
}
|
||||
|
||||
fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
|
||||
let value = E::decode_as_type(&mut &self.bytes[..], &self.ty_id, self.metadata.types())?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
// 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_signed_extensions::ExtrinsicSignedExtensions;
|
||||
use crate::utils::strip_compact_prefix;
|
||||
use crate::{
|
||||
config::Config,
|
||||
error::{BlockError, Error, MetadataError},
|
||||
Metadata,
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use codec::Decode;
|
||||
use scale_decode::DecodeAsType;
|
||||
use subxt_metadata::PalletMetadata;
|
||||
|
||||
pub use crate::blocks::StaticExtrinsic;
|
||||
|
||||
/// The body of a block.
|
||||
pub struct Extrinsics<T: Config> {
|
||||
extrinsics: Vec<Vec<u8>>,
|
||||
metadata: Metadata,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
_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, BlockError> {
|
||||
let ids = ExtrinsicPartTypeIds::new(&metadata)?;
|
||||
|
||||
Ok(Self {
|
||||
extrinsics,
|
||||
metadata,
|
||||
ids,
|
||||
_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 = Result<ExtrinsicDetails<T>, Error>> + Send + Sync + 'static {
|
||||
let extrinsics = self.extrinsics.clone();
|
||||
let num_extrinsics = self.extrinsics.len();
|
||||
let metadata = self.metadata.clone();
|
||||
let ids = self.ids;
|
||||
let mut index = 0;
|
||||
|
||||
core::iter::from_fn(move || {
|
||||
if index == num_extrinsics {
|
||||
None
|
||||
} else {
|
||||
match ExtrinsicDetails::decode_from(
|
||||
index as u32,
|
||||
&extrinsics[index],
|
||||
metadata.clone(),
|
||||
ids,
|
||||
) {
|
||||
Ok(extrinsic_details) => {
|
||||
index += 1;
|
||||
Some(Ok(extrinsic_details))
|
||||
}
|
||||
Err(e) => {
|
||||
index = num_extrinsics;
|
||||
Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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>, Error>> + '_ {
|
||||
self.iter().filter_map(|res| match res {
|
||||
Err(err) => Some(Err(err)),
|
||||
Ok(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>>, Error> {
|
||||
self.find::<E>().next().transpose()
|
||||
}
|
||||
|
||||
/// Iterate through the extrinsics using metadata to dynamically decode and skip
|
||||
/// them, and return the last extrinsic found which decodes to the provided `Ev` type.
|
||||
pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
|
||||
self.find::<E>().last().transpose()
|
||||
}
|
||||
|
||||
/// Find an extrinsics that decodes to the type provided. Returns true if it was found.
|
||||
pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
|
||||
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.
|
||||
bytes: Arc<[u8]>,
|
||||
/// Some if the extrinsic payload is signed.
|
||||
signed_details: Option<SignedExtrinsicDetails>,
|
||||
/// The start index in the `bytes` from which the call is encoded.
|
||||
call_start_idx: usize,
|
||||
/// The pallet index.
|
||||
pallet_index: u8,
|
||||
/// The variant index.
|
||||
variant_index: u8,
|
||||
/// Subxt metadata to fetch the extrinsic metadata.
|
||||
metadata: Metadata,
|
||||
_marker: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Details only available in signed extrinsics.
|
||||
pub struct SignedExtrinsicDetails {
|
||||
/// start index of the range in `bytes` of `ExtrinsicDetails` that encodes the address.
|
||||
address_start_idx: usize,
|
||||
/// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the address. Equivalent to signature_start_idx.
|
||||
address_end_idx: usize,
|
||||
/// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature. Equivalent to extra_start_idx.
|
||||
signature_end_idx: usize,
|
||||
/// end index of the range in `bytes` of `ExtrinsicDetails` that encodes the signature.
|
||||
extra_end_idx: usize,
|
||||
}
|
||||
|
||||
impl<T> ExtrinsicDetails<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
// Attempt to dynamically decode a single extrinsic from the given input.
|
||||
#[doc(hidden)]
|
||||
pub fn decode_from(
|
||||
index: u32,
|
||||
extrinsic_bytes: &[u8],
|
||||
metadata: Metadata,
|
||||
ids: ExtrinsicPartTypeIds,
|
||||
) -> Result<ExtrinsicDetails<T>, Error> {
|
||||
const SIGNATURE_MASK: u8 = 0b1000_0000;
|
||||
const VERSION_MASK: u8 = 0b0111_1111;
|
||||
const LATEST_EXTRINSIC_VERSION: u8 = 4;
|
||||
|
||||
// removing the compact encoded prefix:
|
||||
let bytes: Arc<[u8]> = strip_compact_prefix(extrinsic_bytes)?.1.into();
|
||||
|
||||
// Extrinsic are encoded in memory in the following way:
|
||||
// - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
|
||||
// - signature: [unknown TBD with metadata].
|
||||
// - extrinsic data
|
||||
let first_byte: u8 = Decode::decode(&mut &bytes[..])?;
|
||||
|
||||
let version = first_byte & VERSION_MASK;
|
||||
if version != LATEST_EXTRINSIC_VERSION {
|
||||
return Err(BlockError::UnsupportedVersion(version).into());
|
||||
}
|
||||
|
||||
let is_signed = first_byte & SIGNATURE_MASK != 0;
|
||||
|
||||
// Skip over the first byte which denotes the version and signing.
|
||||
let cursor = &mut &bytes[1..];
|
||||
|
||||
let signed_details = is_signed
|
||||
.then(|| -> Result<SignedExtrinsicDetails, Error> {
|
||||
let address_start_idx = bytes.len() - cursor.len();
|
||||
// Skip over the address, signature and extra fields.
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
&ids.address,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
let address_end_idx = bytes.len() - cursor.len();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
&ids.signature,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
let signature_end_idx = bytes.len() - cursor.len();
|
||||
|
||||
scale_decode::visitor::decode_with_visitor(
|
||||
cursor,
|
||||
&ids.extra,
|
||||
metadata.types(),
|
||||
scale_decode::visitor::IgnoreVisitor::new(),
|
||||
)
|
||||
.map_err(scale_decode::Error::from)?;
|
||||
let extra_end_idx = bytes.len() - cursor.len();
|
||||
|
||||
Ok(SignedExtrinsicDetails {
|
||||
address_start_idx,
|
||||
address_end_idx,
|
||||
signature_end_idx,
|
||||
extra_end_idx,
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let call_start_idx = bytes.len() - cursor.len();
|
||||
|
||||
// Decode the pallet index, then the call variant.
|
||||
let cursor = &mut &bytes[call_start_idx..];
|
||||
|
||||
let pallet_index: u8 = Decode::decode(cursor)?;
|
||||
let variant_index: u8 = Decode::decode(cursor)?;
|
||||
|
||||
Ok(ExtrinsicDetails {
|
||||
index,
|
||||
bytes,
|
||||
signed_details,
|
||||
call_start_idx,
|
||||
pallet_index,
|
||||
variant_index,
|
||||
metadata,
|
||||
_marker: core::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Is the extrinsic signed?
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.signed_details.is_some()
|
||||
}
|
||||
|
||||
/// 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.bytes
|
||||
}
|
||||
|
||||
/// 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.call_start_idx..]
|
||||
}
|
||||
|
||||
/// 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.call_bytes()[2..]
|
||||
}
|
||||
|
||||
/// 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.signed_details
|
||||
.as_ref()
|
||||
.map(|e| &self.bytes[e.address_start_idx..e.address_end_idx])
|
||||
}
|
||||
|
||||
/// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
|
||||
pub fn signature_bytes(&self) -> Option<&[u8]> {
|
||||
self.signed_details
|
||||
.as_ref()
|
||||
.map(|e| &self.bytes[e.address_end_idx..e.signature_end_idx])
|
||||
}
|
||||
|
||||
/// 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 signed_extensions_bytes(&self) -> Option<&[u8]> {
|
||||
self.signed_details
|
||||
.as_ref()
|
||||
.map(|e| &self.bytes[e.signature_end_idx..e.extra_end_idx])
|
||||
}
|
||||
|
||||
/// Returns `None` if the extrinsic is not signed.
|
||||
pub fn signed_extensions(&self) -> Option<ExtrinsicSignedExtensions<'_, T>> {
|
||||
let signed = self.signed_details.as_ref()?;
|
||||
let extra_bytes = &self.bytes[signed.signature_end_idx..signed.extra_end_idx];
|
||||
Some(ExtrinsicSignedExtensions::new(extra_bytes, &self.metadata))
|
||||
}
|
||||
|
||||
/// The index of the pallet that the extrinsic originated from.
|
||||
pub fn pallet_index(&self) -> u8 {
|
||||
self.pallet_index
|
||||
}
|
||||
|
||||
/// The index of the extrinsic variant that the extrinsic originated from.
|
||||
pub fn variant_index(&self) -> u8 {
|
||||
self.variant_index
|
||||
}
|
||||
|
||||
/// The name of the pallet from whence the extrinsic originated.
|
||||
pub fn pallet_name(&self) -> Result<&str, Error> {
|
||||
Ok(self.extrinsic_metadata()?.pallet.name())
|
||||
}
|
||||
|
||||
/// The name of the call (ie the name of the variant that it corresponds to).
|
||||
pub fn variant_name(&self) -> Result<&str, Error> {
|
||||
Ok(&self.extrinsic_metadata()?.variant.name)
|
||||
}
|
||||
|
||||
/// Fetch the metadata for this extrinsic.
|
||||
pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails, Error> {
|
||||
let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
|
||||
let variant = pallet
|
||||
.call_variant_by_index(self.variant_index())
|
||||
.ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
|
||||
|
||||
Ok(ExtrinsicMetadataDetails { pallet, variant })
|
||||
}
|
||||
|
||||
/// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
|
||||
/// type which represents the named or unnamed fields that were present in the extrinsic.
|
||||
pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
|
||||
let bytes = &mut self.field_bytes();
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
|
||||
let mut fields = extrinsic_metadata
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
|
||||
let decoded =
|
||||
scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
|
||||
/// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
|
||||
pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
|
||||
let extrinsic_metadata = self.extrinsic_metadata()?;
|
||||
if extrinsic_metadata.pallet.name() == E::PALLET
|
||||
&& extrinsic_metadata.variant.name == E::CALL
|
||||
{
|
||||
let mut fields = extrinsic_metadata
|
||||
.variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| scale_decode::Field::new(&f.ty.id, f.name.as_deref()));
|
||||
let decoded =
|
||||
E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
|
||||
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, Error> {
|
||||
let decoded = E::decode_as_type(
|
||||
&mut &self.call_bytes()[..],
|
||||
&self.metadata.outer_enums().call_enum_ty(),
|
||||
self.metadata.types(),
|
||||
)?;
|
||||
|
||||
Ok(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Details for the given extrinsic plucked from the metadata.
|
||||
pub struct ExtrinsicMetadataDetails<'a> {
|
||||
/// Metadata for the pallet that the extrinsic belongs to.
|
||||
pub pallet: PalletMetadata<'a>,
|
||||
/// Metadata for the variant which describes the pallet extrinsics.
|
||||
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
|
||||
}
|
||||
|
||||
/// The type IDs extracted from the metadata that represent the
|
||||
/// generic type parameters passed to the `UncheckedExtrinsic` from
|
||||
/// the substrate-based chain.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExtrinsicPartTypeIds {
|
||||
/// The address (source) of the extrinsic.
|
||||
address: u32,
|
||||
/// The extrinsic call type.
|
||||
// Note: the call type can be used to skip over the extrinsic bytes to check
|
||||
// they are in line with our metadata. This operation is currently postponed.
|
||||
_call: u32,
|
||||
/// The signature of the extrinsic.
|
||||
signature: u32,
|
||||
/// The extra parameters of the extrinsic.
|
||||
extra: u32,
|
||||
}
|
||||
|
||||
impl ExtrinsicPartTypeIds {
|
||||
/// Extract the generic type parameters IDs from the extrinsic type.
|
||||
fn new(metadata: &Metadata) -> Result<Self, BlockError> {
|
||||
Ok(ExtrinsicPartTypeIds {
|
||||
address: metadata.extrinsic().address_ty(),
|
||||
_call: metadata.extrinsic().call_ty(),
|
||||
signature: metadata.extrinsic().signature_ty(),
|
||||
extra: metadata.extrinsic().extra_ty(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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::{
|
||||
v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
|
||||
RuntimeMetadataPrefixed,
|
||||
};
|
||||
use scale_info::{meta_type, TypeInfo};
|
||||
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::from(metadata)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrinsic_metadata_consistency() {
|
||||
let metadata = metadata();
|
||||
|
||||
// Except our metadata to contain the registered types.
|
||||
let pallet = metadata.pallet_by_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();
|
||||
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
|
||||
|
||||
// Decode with empty bytes.
|
||||
let result = ExtrinsicDetails::<SubstrateConfig>::decode_from(0, &[], metadata, ids);
|
||||
assert_matches!(result.err(), Some(crate::Error::Codec(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_version_extrinsic() {
|
||||
let metadata = metadata();
|
||||
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
|
||||
|
||||
// Decode with invalid version.
|
||||
let result =
|
||||
ExtrinsicDetails::<SubstrateConfig>::decode_from(0, &vec![3u8].encode(), metadata, ids);
|
||||
|
||||
assert_matches!(
|
||||
result.err(),
|
||||
Some(crate::Error::Block(
|
||||
crate::error::BlockError::UnsupportedVersion(3)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statically_decode_extrinsic() {
|
||||
let metadata = metadata();
|
||||
let ids = ExtrinsicPartTypeIds::new(&metadata).unwrap();
|
||||
|
||||
let tx = crate::dynamic::tx(
|
||||
"Test",
|
||||
"TestCall",
|
||||
vec![
|
||||
Value::u128(10),
|
||||
Value::bool(true),
|
||||
Value::string("SomeValue"),
|
||||
],
|
||||
);
|
||||
let tx_encoded = crate::tx::create_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 extrinsic = ExtrinsicDetails::<SubstrateConfig>::decode_from(
|
||||
1,
|
||||
tx_encoded.encoded(),
|
||||
metadata,
|
||||
ids,
|
||||
)
|
||||
.expect("Valid extrinsic");
|
||||
|
||||
assert!(!extrinsic.is_signed());
|
||||
|
||||
assert_eq!(extrinsic.index(), 1);
|
||||
|
||||
assert_eq!(extrinsic.pallet_index(), 0);
|
||||
assert_eq!(
|
||||
extrinsic
|
||||
.pallet_name()
|
||||
.expect("Valid metadata contains pallet name"),
|
||||
"Test"
|
||||
);
|
||||
|
||||
assert_eq!(extrinsic.variant_index(), 2);
|
||||
assert_eq!(
|
||||
extrinsic
|
||||
.variant_name()
|
||||
.expect("Valid metadata contains variant 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(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+90
-16
@@ -1,19 +1,93 @@
|
||||
use scale_decode::DecodeAsFields;
|
||||
// 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.
|
||||
|
||||
/// 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;
|
||||
//! 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("280402000bf18367a38e01").unwrap(),
|
||||
//! hex::decode("c10184008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4801f4de97941fcc3f95c761cd58d480bb41ce64836850f51b6fcc7542e809eb0a346fe95eb1b72de542273d4f1b00b636eb025e2b0e98cc498a095e7ce48f3d4f82b501040000001848656c6c6f21").unwrap(),
|
||||
//! hex::decode("5102840090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2201ac0c06f55cf3461067bbe48da16efbb50dfad555e2821ce20d37b2e42d6dcb439acd40f742b12ef00f8889944060b04373dc4d34a1992042fd269e8ec1e64a848502000004000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2217000010632d5ec76b05").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() {
|
||||
//! let ext = ext.unwrap();
|
||||
//! println!("Pallet: {}", ext.pallet_name().unwrap());
|
||||
//! println!("Call: {}", ext.variant_name().unwrap());
|
||||
//! }
|
||||
//!
|
||||
//! # let ext_details: Vec<_> = exts.iter()
|
||||
//! # .map(|ext| {
|
||||
//! # let ext = ext.unwrap();
|
||||
//! # let pallet = ext.pallet_name().unwrap().to_string();
|
||||
//! # let call = ext.variant_name().unwrap().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()),
|
||||
//! # ]);
|
||||
//! ```
|
||||
|
||||
/// 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
|
||||
}
|
||||
mod extrinsic_signed_extensions;
|
||||
mod extrinsics;
|
||||
mod static_extrinsic;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::BlockError;
|
||||
use crate::Metadata;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub use extrinsic_signed_extensions::{ExtrinsicSignedExtension, ExtrinsicSignedExtensions};
|
||||
pub use extrinsics::{
|
||||
ExtrinsicDetails, ExtrinsicMetadataDetails, Extrinsics, FoundExtrinsic, SignedExtrinsicDetails,
|
||||
};
|
||||
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>, BlockError> {
|
||||
Extrinsics::decode_from(extrinsics, metadata)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,31 @@
|
||||
// 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::{config::Config, metadata::Metadata};
|
||||
use derive_where::derive_where;
|
||||
|
||||
/// Each client should be able to provide access to the following fields
|
||||
/// - runtime version
|
||||
/// - genesis hash
|
||||
/// - metadata
|
||||
/// This provides access to some relevant client state in signed extensions,
|
||||
/// and is just a combination of some of the available properties.
|
||||
#[derive_where(Clone, Debug)]
|
||||
pub struct ClientState<C: Config> {
|
||||
genesis_hash: C::Hash,
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: Metadata,
|
||||
}
|
||||
|
||||
impl<C: Config> ClientState<C> {
|
||||
pub fn new(genesis_hash: C::Hash, runtime_version: RuntimeVersion, metadata: Metadata) -> Self {
|
||||
Self {
|
||||
genesis_hash,
|
||||
runtime_version,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> Metadata {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
pub fn runtime_version(&self) -> RuntimeVersion {
|
||||
self.runtime_version
|
||||
}
|
||||
|
||||
pub fn genesis_hash(&self) -> C::Hash {
|
||||
self.genesis_hash
|
||||
}
|
||||
/// Genesis hash.
|
||||
pub genesis_hash: C::Hash,
|
||||
/// 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 {
|
||||
spec_version: u32,
|
||||
transaction_version: u32,
|
||||
}
|
||||
|
||||
impl RuntimeVersion {
|
||||
pub fn new(spec_version: u32, transaction_version: u32) -> Self {
|
||||
RuntimeVersion {
|
||||
spec_version,
|
||||
transaction_version,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 fn spec_version(&self) -> u32 {
|
||||
self.spec_version
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
@@ -65,7 +35,5 @@ impl RuntimeVersion {
|
||||
/// dispatchable/module changing its index.
|
||||
///
|
||||
/// It need *not* change when a new module is added or when a dispatchable is added.
|
||||
pub fn transaction_version(&self) -> u32 {
|
||||
self.transaction_version
|
||||
}
|
||||
pub transaction_version: u32,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
@@ -47,7 +47,7 @@ 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()))
|
||||
Ok(CheckSpecVersion(client.runtime_version.spec_version))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +109,7 @@ 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(),
|
||||
))
|
||||
Ok(CheckTxVersion(client.runtime_version.transaction_version))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +133,7 @@ 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()))
|
||||
Ok(CheckGenesis(client.genesis_hash))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,12 +207,12 @@ impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
|
||||
let check_mortality = if let Some(params) = params.0 {
|
||||
CheckMortality {
|
||||
era: params.era,
|
||||
checkpoint: params.checkpoint.unwrap_or(client.genesis_hash()),
|
||||
checkpoint: params.checkpoint.unwrap_or(client.genesis_hash),
|
||||
}
|
||||
} else {
|
||||
CheckMortality {
|
||||
era: Era::Immortal,
|
||||
checkpoint: client.genesis_hash(),
|
||||
checkpoint: client.genesis_hash,
|
||||
}
|
||||
};
|
||||
Ok(check_mortality)
|
||||
@@ -402,7 +400,7 @@ macro_rules! impl_tuples {
|
||||
client: &ClientState<T>,
|
||||
params: Self::Params,
|
||||
) -> Result<Self, ExtrinsicParamsError> {
|
||||
let metadata = client.metadata();
|
||||
let metadata = &client.metadata;
|
||||
let types = metadata.types();
|
||||
|
||||
// For each signed extension in the tuple, find the matching index in the metadata, if
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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 crate::dynamic::DecodedValueThunk;
|
||||
use crate::metadata::DecodeWithMetadata;
|
||||
use alloc::borrow::Cow;
|
||||
@@ -10,7 +12,7 @@ use derive_where::derive_where;
|
||||
|
||||
/// This represents a constant address. Anything implementing this trait
|
||||
/// can be used to fetch constants.
|
||||
pub trait ConstantAddress {
|
||||
pub trait AddressT {
|
||||
/// The target type of the value that lives at this address.
|
||||
type Target: DecodeWithMetadata;
|
||||
|
||||
@@ -78,7 +80,7 @@ impl<ReturnTy> Address<ReturnTy> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReturnTy: DecodeWithMetadata> ConstantAddress for Address<ReturnTy> {
|
||||
impl<ReturnTy: DecodeWithMetadata> AddressT for Address<ReturnTy> {
|
||||
type Target = ReturnTy;
|
||||
|
||||
fn pallet_name(&self) -> &str {
|
||||
+50
-21
@@ -1,29 +1,56 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
//! Types associated with accessing constants.
|
||||
//! 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);
|
||||
//! ```
|
||||
|
||||
mod constant_address;
|
||||
|
||||
pub use constant_address::{dynamic, Address, ConstantAddress, DynamicAddress};
|
||||
pub mod address;
|
||||
|
||||
use address::AddressT;
|
||||
use alloc::borrow::ToOwned;
|
||||
|
||||
use crate::{
|
||||
error::MetadataError,
|
||||
metadata::{DecodeWithMetadata, MetadataExt},
|
||||
Error, Metadata,
|
||||
};
|
||||
use crate::{error::MetadataError, metadata::DecodeWithMetadata, Error, Metadata};
|
||||
|
||||
/// Run validation logic against some constant address you'd like to access. Returns `Ok(())`
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or constant in question do not exist at all).
|
||||
pub fn validate_constant<Address: ConstantAddress>(
|
||||
metadata: &subxt_metadata::Metadata,
|
||||
address: &Address,
|
||||
) -> Result<(), Error> {
|
||||
/// 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<Address: AddressT>(address: &Address, metadata: &Metadata) -> Result<(), Error> {
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let expected_hash = metadata
|
||||
.pallet_by_name_err(address.pallet_name())?
|
||||
@@ -38,12 +65,14 @@ pub fn validate_constant<Address: ConstantAddress>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_constant<Address: ConstantAddress>(
|
||||
metadata: &Metadata,
|
||||
/// 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<Address: AddressT>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Address::Target, Error> {
|
||||
// 1. Validate constant shape if hash given:
|
||||
validate_constant(metadata, address)?;
|
||||
validate(address, metadata)?;
|
||||
|
||||
// 2. Attempt to decode the constant into the type given:
|
||||
let constant = metadata
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
use derive_where::derive_where;
|
||||
// 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 crate::dynamic::DecodedValueThunk;
|
||||
use crate::metadata::DecodeWithMetadata;
|
||||
use crate::utils::Yes;
|
||||
use derive_where::derive_where;
|
||||
|
||||
/// This represents the address of a custom value in the metadata.
|
||||
/// Anything, that implements the [CustomValueAddress] trait can be used, to fetch
|
||||
/// custom values from the metadata.
|
||||
/// The trait is implemented by [str] for dynamic loopup and [StaticAddress] for static queries.
|
||||
pub trait CustomValueAddress {
|
||||
/// Use this with [`AddressT::IsDecodable`].
|
||||
pub use crate::utils::Yes;
|
||||
|
||||
/// This represents the address of a custom value in 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 AddressT {
|
||||
/// The type of the custom value.
|
||||
type Target: DecodeWithMetadata;
|
||||
/// Should be set to `Yes` for Dynamic values and static values that have a valid type.
|
||||
@@ -24,7 +30,7 @@ pub trait CustomValueAddress {
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomValueAddress for str {
|
||||
impl AddressT for str {
|
||||
type Target = DecodedValueThunk;
|
||||
type IsDecodable = Yes;
|
||||
|
||||
@@ -62,7 +68,7 @@ impl<ReturnTy, IsDecodable> StaticAddress<ReturnTy, IsDecodable> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: DecodeWithMetadata, Y> CustomValueAddress for StaticAddress<R, Y> {
|
||||
impl<R: DecodeWithMetadata, Y> AddressT for StaticAddress<R, Y> {
|
||||
type Target = R;
|
||||
type IsDecodable = Y;
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
//! Types associated with accessing custom types
|
||||
//! 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);
|
||||
//! ```
|
||||
|
||||
mod custom_value_address;
|
||||
pub mod address;
|
||||
|
||||
use crate::utils::Yes;
|
||||
pub use custom_value_address::{CustomValueAddress, StaticAddress};
|
||||
|
||||
use crate::{
|
||||
error::MetadataError,
|
||||
metadata::{DecodeWithMetadata, MetadataExt},
|
||||
Error, Metadata,
|
||||
};
|
||||
use crate::{error::MetadataError, metadata::DecodeWithMetadata, Error, Metadata};
|
||||
use address::AddressT;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// 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_custom_value<Address: CustomValueAddress + ?Sized>(
|
||||
metadata: &Metadata,
|
||||
pub fn validate<Address: AddressT + ?Sized>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(actual_hash) = address.validation_hash() {
|
||||
let custom = metadata.custom();
|
||||
@@ -41,12 +62,12 @@ pub fn validate_custom_value<Address: CustomValueAddress + ?Sized>(
|
||||
|
||||
/// 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_custom_value<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>(
|
||||
metadata: &Metadata,
|
||||
pub fn get<Address: AddressT<IsDecodable = Yes> + ?Sized>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Address::Target, Error> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
validate_custom_value(metadata, address)?;
|
||||
validate(address, metadata)?;
|
||||
|
||||
// 2. Attempt to decode custom value:
|
||||
let custom_value = metadata.custom_value_by_name_err(address.name())?;
|
||||
@@ -59,12 +80,12 @@ pub fn get_custom_value<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>
|
||||
}
|
||||
|
||||
/// Access the bytes of a custom value by the address it is registered under.
|
||||
pub fn get_custom_value_bytes<Address: CustomValueAddress + ?Sized>(
|
||||
metadata: &Metadata,
|
||||
pub fn get_bytes<Address: AddressT + ?Sized>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// 1. Validate custom value shape if hash given:
|
||||
validate_custom_value(metadata, address)?;
|
||||
validate(address, metadata)?;
|
||||
|
||||
// 2. Return the underlying bytes:
|
||||
let custom_value = metadata.custom_value_by_name_err(address.name())?;
|
||||
@@ -73,6 +94,8 @@ pub fn get_custom_value_bytes<Address: CustomValueAddress + ?Sized>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use codec::Encode;
|
||||
use scale_decode::DecodeAsType;
|
||||
@@ -83,8 +106,7 @@ mod tests {
|
||||
use alloc::string::String;
|
||||
use alloc::vec;
|
||||
|
||||
use crate::custom_values::get_custom_value;
|
||||
use crate::Metadata;
|
||||
use crate::custom_values;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Encode, TypeInfo, DecodeAsType)]
|
||||
pub struct Person {
|
||||
@@ -135,15 +157,15 @@ mod tests {
|
||||
};
|
||||
|
||||
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
|
||||
Metadata::new(metadata)
|
||||
Metadata::from(metadata)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decoding() {
|
||||
let metadata = mock_metadata();
|
||||
|
||||
assert!(get_custom_value(&metadata, "Invalid Address").is_err());
|
||||
let person_decoded_value_thunk = get_custom_value(&metadata, "Mr. Robot").unwrap();
|
||||
assert!(custom_values::get("Invalid Address", &metadata).is_err());
|
||||
let person_decoded_value_thunk = custom_values::get("Mr. Robot", &metadata).unwrap();
|
||||
let person: Person = person_decoded_value_thunk.as_type().unwrap();
|
||||
assert_eq!(
|
||||
person,
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
@@ -17,16 +17,16 @@ pub use scale_value::{At, Value};
|
||||
pub type DecodedValue = scale_value::Value<u32>;
|
||||
|
||||
// Submit dynamic transactions.
|
||||
pub use crate::tx::dynamic as tx;
|
||||
pub use crate::tx::payload::dynamic as tx;
|
||||
|
||||
// Lookup constants dynamically.
|
||||
pub use crate::constants::dynamic as constant;
|
||||
pub use crate::constants::address::dynamic as constant;
|
||||
|
||||
// Lookup storage values dynamically.
|
||||
pub use crate::storage::dynamic as storage;
|
||||
pub use crate::storage::address::dynamic as storage;
|
||||
|
||||
// Execute runtime API function call dynamically.
|
||||
pub use crate::runtime_api::dynamic as runtime_api_call;
|
||||
pub use crate::runtime_api::payload::dynamic as runtime_api_call;
|
||||
|
||||
/// This is the result of making a dynamic request to a node. From this,
|
||||
/// we can return the raw SCALE bytes that we were handed back, or we can
|
||||
|
||||
+33
-1
@@ -1,15 +1,24 @@
|
||||
// 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 derive_more::{Display, From};
|
||||
use subxt_metadata::StorageHasher;
|
||||
|
||||
/// The error emitted when something goes wrong.
|
||||
#[derive(Debug, Display, From)]
|
||||
pub enum Error {
|
||||
/// Codec error.
|
||||
#[display(fmt = "Scale codec error: {_0}")]
|
||||
Codec(codec::Error),
|
||||
/// Metadata error.
|
||||
#[display(fmt = "Metadata Error: {_0}")]
|
||||
Metadata(MetadataError),
|
||||
/// Storage address error.
|
||||
#[display(fmt = "Storage Error: {_0}")]
|
||||
StorageAddress(StorageAddressError),
|
||||
/// Error decoding to a [`crate::dynamic::Value`].
|
||||
@@ -21,16 +30,39 @@ pub enum Error {
|
||||
/// Error constructing the appropriate extrinsic params.
|
||||
#[display(fmt = "Extrinsic params error: {_0}")]
|
||||
ExtrinsicParams(ExtrinsicParamsError),
|
||||
/// Block body error.
|
||||
#[display(fmt = "Error working with block body: {_0}")]
|
||||
Block(BlockError),
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<scale_decode::visitor::DecodeError> for Error {
|
||||
fn from(value: scale_decode::visitor::DecodeError) -> Self {
|
||||
Error::Decode(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Block error
|
||||
#[derive(Clone, Debug, Display, Eq, PartialEq)]
|
||||
pub enum BlockError {
|
||||
/// Extrinsic type ID cannot be resolved with the provided metadata.
|
||||
#[display(
|
||||
fmt = "Extrinsic type ID cannot be resolved with the provided metadata. Make sure this is a valid metadata"
|
||||
)]
|
||||
MissingType,
|
||||
/// Unsupported signature.
|
||||
#[display(fmt = "Unsupported extrinsic version, only version 4 is supported currently")]
|
||||
/// The extrinsic has an unsupported version.
|
||||
UnsupportedVersion(u8),
|
||||
/// Decoding error.
|
||||
#[display(fmt = "Cannot decode extrinsic: {_0}")]
|
||||
DecodingError(codec::Error),
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for BlockError {}
|
||||
|
||||
/// Something went wrong trying to access details in the metadata.
|
||||
#[derive(Clone, Debug, PartialEq, Display)]
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
// 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 work with events.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::config::PolkadotConfig;
|
||||
//! use subxt_core::events;
|
||||
//! 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_full.scale",
|
||||
//! )]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! // Some metadata we'll use to work with storage entries:
|
||||
//! let metadata_bytes = include_bytes!("../../artifacts/polkadot_metadata_full.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//!
|
||||
//! // Some bytes representing events (located in System.Events storage):
|
||||
//! let event_bytes = hex::decode("1c00000000000000a2e9b53d5517020000000100000000000310c96d901d0102000000020000000408d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a030000000000000000000000000000020000000402d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48102700000000000000000000000000000000020000000407be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25fbeea5a030000000000000000000000000000020000002100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dbeea5a03000000000000000000000000000000000000000000000000000000000000020000000000426df03e00000000").unwrap();
|
||||
//!
|
||||
//! // We can decode these bytes like so:
|
||||
//! let evs = events::decode_from::<PolkadotConfig>(event_bytes, metadata);
|
||||
//!
|
||||
//! // And then do things like iterate over them and inspect details:
|
||||
//! for ev in evs.iter() {
|
||||
//! let ev = ev.unwrap();
|
||||
//! println!("Index: {}", ev.index());
|
||||
//! println!("Name: {}.{}", ev.pallet_name(), ev.variant_name());
|
||||
//! println!("Fields: {:?}", ev.field_values().unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
@@ -7,6 +47,13 @@ use subxt_metadata::PalletMetadata;
|
||||
|
||||
use crate::{error::MetadataError, Config, Error, Metadata};
|
||||
|
||||
/// Create a new [`Events`] instance from the given bytes.
|
||||
///
|
||||
/// This is a shortcut for [`Events::decode_from`].
|
||||
pub fn decode_from<T: Config>(event_bytes: Vec<u8>, metadata: Metadata) -> Events<T> {
|
||||
Events::decode_from(event_bytes, metadata)
|
||||
}
|
||||
|
||||
/// Trait to uniquely identify the events's identity from the runtime metadata.
|
||||
///
|
||||
/// Generated API structures that represent an event implement this trait.
|
||||
@@ -52,7 +99,7 @@ impl<T: Config> core::fmt::Debug for Events<T> {
|
||||
|
||||
impl<T: Config> Events<T> {
|
||||
/// Create a new [`Events`] instance from the given bytes.
|
||||
pub fn decode_from(metadata: Metadata, event_bytes: Vec<u8>) -> Self {
|
||||
pub fn decode_from(event_bytes: Vec<u8>, metadata: Metadata) -> Self {
|
||||
// event_bytes is a SCALE encoded vector of events. So, pluck the
|
||||
// compact encoded length from the front, leaving the remaining bytes
|
||||
// for our iterating to decode.
|
||||
@@ -85,6 +132,11 @@ impl<T: Config> Events<T> {
|
||||
self.num_events == 0
|
||||
}
|
||||
|
||||
/// Return the bytes representing all of the events.
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.event_bytes
|
||||
}
|
||||
|
||||
/// Iterate over all of the events, using metadata to dynamically
|
||||
/// decode them as we go, and returning the raw bytes and other associated
|
||||
/// details. If an error occurs, all subsequent iterations return `None`.
|
||||
@@ -276,12 +328,12 @@ impl<T: Config> EventDetails<T> {
|
||||
|
||||
/// The name of the pallet from whence the Event originated.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
self.event_metadata().pallet().name()
|
||||
self.event_metadata().pallet.name()
|
||||
}
|
||||
|
||||
/// The name of the event (ie the name of the variant that it corresponds to).
|
||||
pub fn variant_name(&self) -> &str {
|
||||
&self.event_metadata().variant().name
|
||||
&self.event_metadata().variant.name
|
||||
}
|
||||
|
||||
/// Fetch details from the metadata for this event.
|
||||
@@ -370,17 +422,10 @@ impl<T: Config> EventDetails<T> {
|
||||
|
||||
/// Details for the given event plucked from the metadata.
|
||||
pub struct EventMetadataDetails<'a> {
|
||||
pallet: PalletMetadata<'a>,
|
||||
variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
|
||||
}
|
||||
|
||||
impl<'a> EventMetadataDetails<'a> {
|
||||
pub fn pallet(&self) -> PalletMetadata<'a> {
|
||||
self.pallet
|
||||
}
|
||||
pub fn variant(&self) -> &'a scale_info::Variant<scale_info::form::PortableForm> {
|
||||
self.variant
|
||||
}
|
||||
/// Metadata for the pallet that the event belongs to.
|
||||
pub pallet: PalletMetadata<'a>,
|
||||
/// Metadata for the variant which describes the pallet events.
|
||||
pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
|
||||
}
|
||||
|
||||
/// Event related test utilities used outside this module.
|
||||
@@ -504,8 +549,9 @@ pub(crate) mod test_utils {
|
||||
},
|
||||
);
|
||||
let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
|
||||
let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
|
||||
|
||||
Metadata::new(runtime_metadata.try_into().unwrap())
|
||||
Metadata::from(metadata)
|
||||
}
|
||||
|
||||
/// Build an `Events` object for test purposes, based on the details provided,
|
||||
@@ -532,7 +578,7 @@ pub(crate) mod test_utils {
|
||||
// Prepend compact encoded length to event bytes:
|
||||
let mut all_event_bytes = Compact(num_events).encode();
|
||||
all_event_bytes.extend(event_bytes);
|
||||
Events::decode_from(metadata, all_event_bytes)
|
||||
Events::decode_from(all_event_bytes, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-5
@@ -2,13 +2,30 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Subxt-core
|
||||
//! # 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.
|
||||
//! - [`metadata`]: decode bytes into the metadata used throughout this library.
|
||||
//! - [`storage`]: construct storage request payloads and decode the results you'd get back.
|
||||
//! - [`tx`]: construct and sign transactions (extrinsics).
|
||||
//! - [`runtime_api`]: construct runtime API request payloads and decode the results you'd get back.
|
||||
//! - [`events`]: decode and explore events.
|
||||
//!
|
||||
//! `#[no_std]` compatible core crate for subxt.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
pub extern crate alloc;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod blocks;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
@@ -27,13 +44,12 @@ pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use metadata::Metadata;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
/// 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;
|
||||
|
||||
cfg_substrate_compat! {
|
||||
pub use sp_runtime;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// 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.
|
||||
|
||||
macro_rules! cfg_feature {
|
||||
($feature:literal, $($item:item)*) => {
|
||||
$(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
@@ -21,12 +21,6 @@ impl core::ops::Deref for Metadata {
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn new(md: subxt_metadata::Metadata) -> Self {
|
||||
Metadata {
|
||||
inner: Arc::new(md),
|
||||
}
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
|
||||
pub fn pallet_by_name_err(
|
||||
&self,
|
||||
@@ -53,11 +47,23 @@ impl Metadata {
|
||||
self.runtime_api_trait_by_name(name)
|
||||
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.custom().get(name)`, but returns an error if the trait is not found.
|
||||
pub fn custom_value_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError> {
|
||||
self.custom()
|
||||
.get(name)
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<subxt_metadata::Metadata> for Metadata {
|
||||
fn from(md: subxt_metadata::Metadata) -> Self {
|
||||
Metadata::new(md)
|
||||
Metadata {
|
||||
inner: Arc::new(md),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,68 +76,6 @@ impl TryFrom<frame_metadata::RuntimeMetadataPrefixed> for Metadata {
|
||||
|
||||
impl codec::Decode for Metadata {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
|
||||
subxt_metadata::Metadata::decode(input).map(Metadata::new)
|
||||
}
|
||||
}
|
||||
|
||||
/// Some extension methods on [`subxt_metadata::Metadata`] that return Errors instead of Options.
|
||||
pub trait MetadataExt {
|
||||
fn pallet_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
|
||||
|
||||
fn pallet_by_index_err(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError>;
|
||||
|
||||
fn runtime_api_trait_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError>;
|
||||
|
||||
fn custom_value_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError>;
|
||||
}
|
||||
|
||||
impl MetadataExt for subxt_metadata::Metadata {
|
||||
/// Identical to `metadata.pallet_by_name()`, but returns an error if the pallet is not found.
|
||||
fn pallet_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
|
||||
self.pallet_by_name(name)
|
||||
.ok_or_else(|| MetadataError::PalletNameNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.pallet_by_index()`, but returns an error if the pallet is not found.
|
||||
fn pallet_by_index_err(
|
||||
&self,
|
||||
index: u8,
|
||||
) -> Result<subxt_metadata::PalletMetadata, MetadataError> {
|
||||
self.pallet_by_index(index)
|
||||
.ok_or(MetadataError::PalletIndexNotFound(index))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
|
||||
fn runtime_api_trait_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::RuntimeApiMetadata, MetadataError> {
|
||||
self.runtime_api_trait_by_name(name)
|
||||
.ok_or_else(|| MetadataError::RuntimeTraitNotFound(name.to_owned()))
|
||||
}
|
||||
|
||||
/// Identical to `metadata.runtime_api_trait_by_name()`, but returns an error if the trait is not found.
|
||||
fn custom_value_by_name_err(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<subxt_metadata::CustomValueMetadata, MetadataError> {
|
||||
self.custom()
|
||||
.get(name)
|
||||
.ok_or_else(|| MetadataError::CustomValueNameNotFound(name.to_owned()))
|
||||
subxt_metadata::Metadata::decode(input).map(Metadata::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,34 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
//! Types representing the metadata obtained from a node.
|
||||
//! A [`Metadata`] type, which is used through this crate.
|
||||
//!
|
||||
//! This can be decoded from the bytes handed back from a node when asking for metadata.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use subxt_core::metadata;
|
||||
//!
|
||||
//! // We need to fetch the bytes from somewhere, and then we can decode them:
|
||||
//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
|
||||
//! let metadata = metadata::decode_from(&metadata_bytes[..]).unwrap();
|
||||
//! ```
|
||||
|
||||
mod decode_encode_traits;
|
||||
mod metadata_type;
|
||||
|
||||
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
|
||||
pub use metadata_type::{Metadata, MetadataExt};
|
||||
use codec::Decode;
|
||||
|
||||
// Expose metadata types under a sub module in case somebody needs to reference them:
|
||||
pub use subxt_metadata as types;
|
||||
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
|
||||
pub use metadata_type::Metadata;
|
||||
|
||||
/// Attempt to decode some bytes into [`Metadata`], returning an error
|
||||
/// if decoding fails.
|
||||
///
|
||||
/// This is a shortcut for importing [`codec::Decode`] and using the
|
||||
/// implementation of that on [`Metadata`].
|
||||
pub fn decode_from(bytes: &[u8]) -> Result<Metadata, codec::Error> {
|
||||
Metadata::decode(&mut &*bytes)
|
||||
}
|
||||
|
||||
+86
-166
@@ -1,185 +1,105 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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::borrow::Cow;
|
||||
//! 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::error::{Error, MetadataError};
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
use derive_where::derive_where;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::Composite;
|
||||
use payload::PayloadT;
|
||||
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::error::MetadataError;
|
||||
use crate::Error;
|
||||
/// 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<Payload: PayloadT>(payload: &Payload, metadata: &Metadata) -> Result<(), Error> {
|
||||
let Some(static_hash) = payload.validation_hash() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
|
||||
|
||||
/// This represents a runtime API payload that can call into the runtime of node.
|
||||
///
|
||||
/// # Components
|
||||
///
|
||||
/// - associated return type
|
||||
///
|
||||
/// Resulting bytes of the call are interpreted into this type.
|
||||
///
|
||||
/// - runtime function name
|
||||
///
|
||||
/// The function name of the runtime API call. This is obtained by concatenating
|
||||
/// the runtime trait name with the trait's method.
|
||||
///
|
||||
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
|
||||
/// contains the `metadata_at_version` function. The corresponding runtime function
|
||||
/// is `Metadata_metadata_at_version`.
|
||||
///
|
||||
/// - encoded arguments
|
||||
///
|
||||
/// Each argument of the runtime function must be scale-encoded.
|
||||
pub trait RuntimeApiPayload {
|
||||
/// The return type of the function call.
|
||||
// Note: `DecodeWithMetadata` is needed to decode the function call result
|
||||
// with the `subxt::Metadata.
|
||||
type ReturnType: DecodeWithMetadata;
|
||||
|
||||
/// The runtime API trait name.
|
||||
fn trait_name(&self) -> &str;
|
||||
|
||||
/// The runtime API method name.
|
||||
fn method_name(&self) -> &str;
|
||||
|
||||
/// Scale encode the arguments data.
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
|
||||
/// Encode arguments data and return the output. This is a convenience
|
||||
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
|
||||
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
let mut v = Vec::new();
|
||||
self.encode_args_to(metadata, &mut v)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
let Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
if static_hash != runtime_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A runtime API payload containing the generic argument data
|
||||
/// and interpreting the result of the call as `ReturnTy`.
|
||||
///
|
||||
/// This can be created from static values (ie those generated
|
||||
/// via the `subxt` macro) or dynamic values via [`dynamic`].
|
||||
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
|
||||
pub struct Payload<ArgsData, ReturnTy> {
|
||||
trait_name: Cow<'static, str>,
|
||||
method_name: Cow<'static, str>,
|
||||
args_data: ArgsData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnTy>,
|
||||
/// Return the name of the runtime API call from the payload.
|
||||
pub fn call_name<Payload: PayloadT>(payload: &Payload) -> String {
|
||||
format!("{}_{}", payload.trait_name(), payload.method_name())
|
||||
}
|
||||
|
||||
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
|
||||
for Payload<ArgsData, ReturnTy>
|
||||
{
|
||||
type ReturnType = ReturnTy;
|
||||
|
||||
fn trait_name(&self) -> &str {
|
||||
&self.trait_name
|
||||
}
|
||||
|
||||
fn method_name(&self) -> &str {
|
||||
&self.method_name
|
||||
}
|
||||
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let api_method = metadata
|
||||
.runtime_api_trait_by_name_err(&self.trait_name)?
|
||||
.method_by_name(&self.method_name)
|
||||
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
|
||||
let mut fields = api_method
|
||||
.inputs()
|
||||
.map(|input| scale_encode::Field::named(&input.ty, &input.name));
|
||||
|
||||
self.args_data
|
||||
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
/// Return the encoded call args given a runtime API payload.
|
||||
pub fn call_args<Payload: PayloadT>(
|
||||
payload: &Payload,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
payload.encode_args(metadata)
|
||||
}
|
||||
|
||||
/// A dynamic runtime API payload.
|
||||
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
|
||||
/// Decode the value bytes at the location given by the provided runtime API payload.
|
||||
pub fn decode_value<Payload: PayloadT>(
|
||||
bytes: &mut &[u8],
|
||||
payload: &Payload,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Payload::ReturnType, Error> {
|
||||
let api_method = metadata
|
||||
.runtime_api_trait_by_name_err(payload.trait_name())?
|
||||
.method_by_name(payload.method_name())
|
||||
.ok_or_else(|| MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned()))?;
|
||||
|
||||
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
|
||||
/// Create a new [`Payload`].
|
||||
pub fn new(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: ArgsData,
|
||||
) -> Self {
|
||||
Payload {
|
||||
trait_name: Cow::Owned(trait_name.into()),
|
||||
method_name: Cow::Owned(method_name.into()),
|
||||
args_data,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
let val = <Payload::ReturnType as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut &bytes[..],
|
||||
api_method.output_ty(),
|
||||
metadata,
|
||||
)?;
|
||||
|
||||
/// Create a new static [`Payload`] 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_data: ArgsData,
|
||||
hash: [u8; 32],
|
||||
) -> Payload<ArgsData, ReturnTy> {
|
||||
Payload {
|
||||
trait_name: Cow::Borrowed(trait_name),
|
||||
method_name: Cow::Borrowed(method_name),
|
||||
args_data,
|
||||
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
|
||||
}
|
||||
|
||||
/// Returns the arguments data.
|
||||
pub fn args_data(&self) -> &ArgsData {
|
||||
&self.args_data
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicRuntimeApiPayload`].
|
||||
pub fn dynamic(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: impl Into<Composite<()>>,
|
||||
) -> DynamicRuntimeApiPayload {
|
||||
Payload::new(trait_name, method_name, args_data.into())
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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::borrow::ToOwned;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
use derive_where::derive_where;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::Composite;
|
||||
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::error::MetadataError;
|
||||
use crate::Error;
|
||||
|
||||
use crate::metadata::{DecodeWithMetadata, Metadata};
|
||||
|
||||
/// This represents a runtime API payload that can call into the runtime of node.
|
||||
///
|
||||
/// # Components
|
||||
///
|
||||
/// - associated return type
|
||||
///
|
||||
/// Resulting bytes of the call are interpreted into this type.
|
||||
///
|
||||
/// - runtime function name
|
||||
///
|
||||
/// The function name of the runtime API call. This is obtained by concatenating
|
||||
/// the runtime trait name with the trait's method.
|
||||
///
|
||||
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
|
||||
/// contains the `metadata_at_version` function. The corresponding runtime function
|
||||
/// is `Metadata_metadata_at_version`.
|
||||
///
|
||||
/// - encoded arguments
|
||||
///
|
||||
/// Each argument of the runtime function must be scale-encoded.
|
||||
pub trait PayloadT {
|
||||
/// The return type of the function call.
|
||||
// Note: `DecodeWithMetadata` is needed to decode the function call result
|
||||
// with the `subxt::Metadata.
|
||||
type ReturnType: DecodeWithMetadata;
|
||||
|
||||
/// The runtime API trait name.
|
||||
fn trait_name(&self) -> &str;
|
||||
|
||||
/// The runtime API method name.
|
||||
fn method_name(&self) -> &str;
|
||||
|
||||
/// Scale encode the arguments data.
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
|
||||
/// Encode arguments data and return the output. This is a convenience
|
||||
/// wrapper around [`PayloadT::encode_args_to`].
|
||||
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
let mut v = Vec::new();
|
||||
self.encode_args_to(metadata, &mut v)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime API payload containing the generic argument data
|
||||
/// and interpreting the result of the call as `ReturnTy`.
|
||||
///
|
||||
/// This can be created from static values (ie those generated
|
||||
/// via the `subxt` macro) or dynamic values via [`dynamic`].
|
||||
#[derive_where(Clone, Debug, Eq, Ord, PartialEq, PartialOrd; ArgsData)]
|
||||
pub struct Payload<ArgsData, ReturnTy> {
|
||||
trait_name: Cow<'static, str>,
|
||||
method_name: Cow<'static, str>,
|
||||
args_data: ArgsData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnTy>,
|
||||
}
|
||||
|
||||
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> PayloadT
|
||||
for Payload<ArgsData, ReturnTy>
|
||||
{
|
||||
type ReturnType = ReturnTy;
|
||||
|
||||
fn trait_name(&self) -> &str {
|
||||
&self.trait_name
|
||||
}
|
||||
|
||||
fn method_name(&self) -> &str {
|
||||
&self.method_name
|
||||
}
|
||||
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let api_method = metadata
|
||||
.runtime_api_trait_by_name_err(&self.trait_name)?
|
||||
.method_by_name(&self.method_name)
|
||||
.ok_or_else(|| MetadataError::RuntimeMethodNotFound((*self.method_name).to_owned()))?;
|
||||
let mut fields = api_method
|
||||
.inputs()
|
||||
.map(|input| scale_encode::Field::named(&input.ty, &input.name));
|
||||
|
||||
self.args_data
|
||||
.encode_as_fields_to(&mut fields, metadata.types(), out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic runtime API payload.
|
||||
pub type DynamicPayload = Payload<Composite<()>, DecodedValueThunk>;
|
||||
|
||||
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
|
||||
/// Create a new [`Payload`].
|
||||
pub fn new(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: ArgsData,
|
||||
) -> Self {
|
||||
Payload {
|
||||
trait_name: Cow::Owned(trait_name.into()),
|
||||
method_name: Cow::Owned(method_name.into()),
|
||||
args_data,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new static [`Payload`] 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_data: ArgsData,
|
||||
hash: [u8; 32],
|
||||
) -> Payload<ArgsData, ReturnTy> {
|
||||
Payload {
|
||||
trait_name: Cow::Borrowed(trait_name),
|
||||
method_name: Cow::Borrowed(method_name),
|
||||
args_data,
|
||||
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
|
||||
}
|
||||
|
||||
/// Returns the arguments data.
|
||||
pub fn args_data(&self) -> &ArgsData {
|
||||
&self.args_data
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicPayload`].
|
||||
pub fn dynamic(
|
||||
trait_name: impl Into<String>,
|
||||
method_name: impl Into<String>,
|
||||
args_data: impl Into<Composite<()>>,
|
||||
) -> DynamicPayload {
|
||||
Payload::new(trait_name, method_name, args_data.into())
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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::{
|
||||
dynamic::DecodedValueThunk,
|
||||
error::{Error, MetadataError},
|
||||
@@ -14,11 +16,12 @@ use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{storage_key::StorageHashers, StorageKey};
|
||||
// Re-export types used here:
|
||||
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageHashersIter, StorageKey};
|
||||
|
||||
/// This represents a storage address. Anything implementing this trait
|
||||
/// can be used to fetch and iterate over storage entries.
|
||||
pub trait StorageAddress {
|
||||
pub trait AddressT {
|
||||
/// The target type of the value that lives at this address.
|
||||
type Target: DecodeWithMetadata;
|
||||
/// The keys type used to construct this address.
|
||||
@@ -120,11 +123,11 @@ where
|
||||
|
||||
/// Return bytes representing the root of this storage entry (a hash of the pallet and entry name).
|
||||
pub fn to_root_bytes(&self) -> Vec<u8> {
|
||||
super::utils::storage_address_root_bytes(self)
|
||||
super::get_address_root_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> StorageAddress
|
||||
impl<Keys, ReturnTy, Fetchable, Defaultable, Iterable> AddressT
|
||||
for Address<Keys, ReturnTy, Fetchable, Defaultable, Iterable>
|
||||
where
|
||||
Keys: StorageKey,
|
||||
+133
-13
@@ -1,22 +1,142 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
//! Types associated with accessing and working with storage items.
|
||||
//! 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 account = dev::alice().public_key().into();
|
||||
//! let address = polkadot::storage().system().account(&account);
|
||||
//!
|
||||
//! // We can validate that the address is compatible with the given metadata.
|
||||
//! storage::validate(&address, &metadata).unwrap();
|
||||
//!
|
||||
//! // Encode the address to bytes. These can be sent to a node to query the value.
|
||||
//! storage::get_address_bytes(&address, &metadata).unwrap();
|
||||
//!
|
||||
//! // If we were to obtain a value back from the node at that address, we could
|
||||
//! // then decode it using the same address and metadata like so:
|
||||
//! let value_bytes = hex::decode("00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080").unwrap();
|
||||
//! let value = storage::decode_value(&mut &*value_bytes, &address, &metadata).unwrap();
|
||||
//!
|
||||
//! println!("Alice's account info: {value:?}");
|
||||
//! ```
|
||||
|
||||
mod storage_address;
|
||||
mod storage_key;
|
||||
pub mod utils;
|
||||
mod utils;
|
||||
|
||||
/// Types representing an address which describes where a storage
|
||||
/// entry lives and how to properly decode it.
|
||||
pub mod address {
|
||||
pub use super::storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
|
||||
pub use super::storage_key::{StaticStorageKey, StorageHashers, StorageKey};
|
||||
pub mod address;
|
||||
|
||||
use crate::{error::MetadataError, metadata::DecodeWithMetadata, Error, Metadata};
|
||||
use address::AddressT;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
// This isn't a part of the public API, but expose here because it's useful in Subxt.
|
||||
#[doc(hidden)]
|
||||
pub use utils::lookup_storage_entry_details;
|
||||
|
||||
/// 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<Address: AddressT>(address: &Address, metadata: &Metadata) -> Result<(), Error> {
|
||||
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_err(pallet_name)?;
|
||||
|
||||
let Some(expected_hash) = pallet_metadata.storage_hash(entry_name) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
if expected_hash != hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub use storage_key::StorageKey;
|
||||
/// Given a storage address and some metadata, this encodes the address into bytes which can be
|
||||
/// handed to a node to retrieve the corresponding value.
|
||||
pub fn get_address_bytes<Address: AddressT>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = Vec::new();
|
||||
utils::write_storage_address_root_bytes(address, &mut bytes);
|
||||
address.append_entry_bytes(metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
// For consistency with other modules, also expose
|
||||
// the basic address stuff at the root of the module.
|
||||
pub use storage_address::{dynamic, Address, DynamicAddress, StorageAddress};
|
||||
/// Given a storage address and some metadata, this encodes the root of the address (ie the pallet
|
||||
/// and storage entry part) into bytes. If the entry being addressed is inside a map, this returns
|
||||
/// the bytes needed to iterate over all of the entries within it.
|
||||
pub fn get_address_root_bytes<Address: AddressT>(address: &Address) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
utils::write_storage_address_root_bytes(address, &mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Given some storage value that we've retrieved from a node, the address used to retrieve it, and
|
||||
/// metadata from the node, this function attempts to decode the bytes into the target value specified
|
||||
/// by the address.
|
||||
pub fn decode_value<Address: AddressT>(
|
||||
bytes: &mut &[u8],
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Address::Target, Error> {
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
|
||||
let (_, entry_metadata) =
|
||||
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
|
||||
let value_ty_id = match entry_metadata.entry_type() {
|
||||
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
|
||||
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
|
||||
};
|
||||
|
||||
let val = Address::Target::decode_with_metadata(bytes, value_ty_id, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Return the default value at a given storage address if one is available, or an error otherwise.
|
||||
pub fn default_value<Address: AddressT>(
|
||||
address: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Address::Target, Error> {
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
|
||||
let (_, entry_metadata) =
|
||||
utils::lookup_storage_entry_details(pallet_name, entry_name, metadata)?;
|
||||
let value_ty_id = match entry_metadata.entry_type() {
|
||||
subxt_metadata::StorageEntryType::Plain(ty) => *ty,
|
||||
subxt_metadata::StorageEntryType::Map { value_ty, .. } => *value_ty,
|
||||
};
|
||||
|
||||
let default_bytes = entry_metadata.default_bytes();
|
||||
let val = Address::Target::decode_with_metadata(&mut &*default_bytes, value_ty_id, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// 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::utils::hash_bytes;
|
||||
use crate::{
|
||||
error::{Error, MetadataError, StorageAddressError},
|
||||
|
||||
+11
-73
@@ -1,50 +1,25 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2019-2024 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! these utility methods complement the [`StorageAddress`] trait, but
|
||||
//! these utility methods complement the [`AddressT`] trait, but
|
||||
//! aren't things that should ever be overridden, and so don't exist on
|
||||
//! the trait itself.
|
||||
|
||||
use crate::error::MetadataError;
|
||||
use crate::metadata::{DecodeWithMetadata, MetadataExt};
|
||||
use alloc::vec::Vec;
|
||||
use subxt_metadata::PalletMetadata;
|
||||
use subxt_metadata::{StorageEntryMetadata, StorageHasher};
|
||||
|
||||
use super::StorageAddress;
|
||||
use crate::{error::Error, metadata::Metadata};
|
||||
use super::address::AddressT;
|
||||
use crate::error::{Error, MetadataError};
|
||||
use crate::metadata::Metadata;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::vec::Vec;
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageHasher};
|
||||
|
||||
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
|
||||
/// Return the root of a given [`AddressT`]: hash the pallet name and entry name
|
||||
/// and append those bytes to the output.
|
||||
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
|
||||
addr: &Address,
|
||||
out: &mut Vec<u8>,
|
||||
) {
|
||||
pub fn write_storage_address_root_bytes<Address: AddressT>(addr: &Address, out: &mut Vec<u8>) {
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
|
||||
}
|
||||
|
||||
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
|
||||
/// a lookup in a storage map at that location.
|
||||
pub fn storage_address_bytes<Address: StorageAddress>(
|
||||
addr: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = Vec::new();
|
||||
write_storage_address_root_bytes(addr, &mut bytes);
|
||||
addr.append_entry_bytes(metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
|
||||
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
write_storage_address_root_bytes(addr, &mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
|
||||
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
|
||||
match hasher {
|
||||
@@ -65,10 +40,10 @@ pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
|
||||
}
|
||||
|
||||
/// Return details about the given storage entry.
|
||||
pub fn lookup_entry_details<'a>(
|
||||
pub fn lookup_storage_entry_details<'a>(
|
||||
pallet_name: &str,
|
||||
entry_name: &str,
|
||||
metadata: &'a subxt_metadata::Metadata,
|
||||
metadata: &'a Metadata,
|
||||
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
|
||||
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
|
||||
let storage_metadata = pallet_metadata
|
||||
@@ -79,40 +54,3 @@ pub fn lookup_entry_details<'a>(
|
||||
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
|
||||
Ok((pallet_metadata, storage_entry))
|
||||
}
|
||||
|
||||
/// Validate a storage address against the metadata.
|
||||
pub fn validate_storage_address<Address: StorageAddress>(
|
||||
address: &Address,
|
||||
pallet: PalletMetadata<'_>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(hash) = address.validation_hash() {
|
||||
validate_storage(pallet, address.entry_name(), hash)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a storage entry against the metadata.
|
||||
fn validate_storage(
|
||||
pallet: PalletMetadata<'_>,
|
||||
storage_name: &str,
|
||||
hash: [u8; 32],
|
||||
) -> Result<(), Error> {
|
||||
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
if expected_hash != hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given some bytes, a pallet and storage name, decode the response.
|
||||
pub fn decode_storage_with_metadata<T: DecodeWithMetadata>(
|
||||
bytes: &mut &[u8],
|
||||
metadata: &Metadata,
|
||||
storage_metadata: &StorageEntryMetadata,
|
||||
) -> Result<T, Error> {
|
||||
let return_ty = storage_metadata.entry_type().value_ty();
|
||||
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
+275
-147
@@ -1,180 +1,308 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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::error::MetadataError;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::Error;
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::string::String;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::Encode;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::{Composite, Value, ValueDef, Variant};
|
||||
//! Construct and sign transactions.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//! use subxt_macro::subxt;
|
||||
//! use subxt_core::config::PolkadotConfig;
|
||||
//! 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_signed(&call, &state, &dev::alice(), params).unwrap();
|
||||
//!
|
||||
//! // And log it:
|
||||
//! println!("Tx: 0x{}", hex::encode(signed_call.encoded()));
|
||||
//! ```
|
||||
|
||||
pub mod payload;
|
||||
pub mod signer;
|
||||
pub use signer::Signer;
|
||||
|
||||
/// This represents a transaction payload that can be submitted
|
||||
/// to a node.
|
||||
pub trait TxPayload {
|
||||
/// Encode call data to the provided output.
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher};
|
||||
use crate::error::{Error, MetadataError};
|
||||
use crate::metadata::Metadata;
|
||||
use crate::utils::Encoded;
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Compact, Encode};
|
||||
use payload::PayloadT;
|
||||
use signer::Signer as SignerT;
|
||||
use sp_crypto_hashing::blake2_256;
|
||||
|
||||
/// Encode call data and return the output. This is a convenience
|
||||
/// wrapper around [`TxPayload::encode_call_data_to`].
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
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
|
||||
// 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: PayloadT>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
|
||||
if let Some(details) = call.validation_details() {
|
||||
let expected_hash = metadata
|
||||
.pallet_by_name_err(details.pallet_name)?
|
||||
.call_hash(details.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
|
||||
|
||||
if details.hash != expected_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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],
|
||||
/// Return the SCALE encoded bytes representing the call data of the transaction.
|
||||
pub fn call_data<Call: PayloadT>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = Vec::new();
|
||||
call.encode_call_data_to(metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// A transaction payload containing some generic `CallData`.
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Payload<CallData> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
call_name: Cow<'static, str>,
|
||||
call_data: CallData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
/// Creates an unsigned extrinsic without submitting it.
|
||||
pub fn create_unsigned<T: Config, Call: PayloadT>(
|
||||
call: &Call,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Transaction<T>, Error> {
|
||||
// 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();
|
||||
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
|
||||
4u8.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))
|
||||
}
|
||||
|
||||
/// The type of a payload typically used for dynamic transaction payloads.
|
||||
pub type DynamicPayload = Payload<Composite<()>>;
|
||||
/// Create a partial extrinsic.
|
||||
///
|
||||
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
|
||||
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
|
||||
pub fn create_partial_signed<T: Config, Call: PayloadT>(
|
||||
call: &Call,
|
||||
client_state: &ClientState<T>,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialTransaction<T>, Error> {
|
||||
// 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)?;
|
||||
|
||||
impl<CallData> Payload<CallData> {
|
||||
/// Create a new [`Payload`].
|
||||
pub fn new(
|
||||
pallet_name: impl Into<String>,
|
||||
call_name: impl Into<String>,
|
||||
call_data: CallData,
|
||||
) -> Self {
|
||||
Payload {
|
||||
pallet_name: Cow::Owned(pallet_name.into()),
|
||||
call_name: Cow::Owned(call_name.into()),
|
||||
call_data,
|
||||
validation_hash: None,
|
||||
// 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(PartialTransaction {
|
||||
call_data,
|
||||
additional_and_extra_params,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a signed extrinsic without submitting it.
|
||||
///
|
||||
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
|
||||
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.
|
||||
pub fn create_signed<T, Call, Signer>(
|
||||
call: &Call,
|
||||
client_state: &ClientState<T>,
|
||||
signer: &Signer,
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<Transaction<T>, Error>
|
||||
where
|
||||
T: Config,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
// 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. Gather the "additional" and "extra" params along with the encoded call data,
|
||||
// ready to be signed.
|
||||
let partial_signed = create_partial_signed(call, client_state, params)?;
|
||||
|
||||
// 3. Sign and construct an extrinsic from these details.
|
||||
Ok(partial_signed.sign(signer))
|
||||
}
|
||||
|
||||
/// This represents a partially constructed transaction that needs signing before it is ready
|
||||
/// to submit. Use [`PartialTransaction::signer_payload()`] to return the payload that needs signing,
|
||||
/// [`PartialTransaction::sign()`] to sign the transaction using a [`SignerT`] impl, or
|
||||
/// [`PartialTransaction::sign_with_address_and_signature()`] to apply an existing signature and address
|
||||
/// to the transaction.
|
||||
pub struct PartialTransaction<T: Config> {
|
||||
call_data: Vec<u8>,
|
||||
additional_and_extra_params: T::ExtrinsicParams,
|
||||
}
|
||||
|
||||
impl<T: Config> PartialTransaction<T> {
|
||||
// Obtain bytes representing the signer payload and run call some function
|
||||
// with them. This can avoid an allocation in some cases when compared to
|
||||
// [`PartialExtrinsic::signer_payload()`].
|
||||
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_extra_to(&mut bytes);
|
||||
self.additional_and_extra_params
|
||||
.encode_additional_to(&mut bytes);
|
||||
if bytes.len() > 256 {
|
||||
f(Cow::Borrowed(blake2_256(&bytes).as_ref()))
|
||||
} else {
|
||||
f(Cow::Owned(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Payload`] 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 {
|
||||
Payload {
|
||||
pallet_name: Cow::Borrowed(pallet_name),
|
||||
call_name: Cow::Borrowed(call_name),
|
||||
call_data,
|
||||
validation_hash: Some(validation_hash),
|
||||
}
|
||||
/// Return the 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())
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Return the bytes representing the call data for this partially constructed
|
||||
/// extrinsic.
|
||||
pub fn call_data(&self) -> &[u8] {
|
||||
&self.call_data
|
||||
}
|
||||
|
||||
/// Returns the pallet name.
|
||||
pub fn pallet_name(&self) -> &str {
|
||||
&self.pallet_name
|
||||
/// Convert this [`PartialTransaction`] into a [`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_address_and_signature(&signer.address(), &signature)
|
||||
}
|
||||
|
||||
/// Returns the call name.
|
||||
pub fn call_name(&self) -> &str {
|
||||
&self.call_name
|
||||
}
|
||||
}
|
||||
|
||||
impl Payload<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,
|
||||
}),
|
||||
/// Convert this [`PartialTransaction`] into a [`Transaction`], ready to submit.
|
||||
/// An address, and something representing a signature that can be SCALE encoded, are both
|
||||
/// needed in order to construct it. If you have a `Signer` to hand, you can use
|
||||
/// [`PartialTransaction::sign()`] instead.
|
||||
pub fn sign_with_address_and_signature(
|
||||
&self,
|
||||
address: &T::Address,
|
||||
signature: &T::Signature,
|
||||
) -> Transaction<T> {
|
||||
// Encode the extrinsic (into the format expected by protocol version 4)
|
||||
let extrinsic = {
|
||||
let mut encoded_inner = Vec::new();
|
||||
// "is signed" + transaction protocol version (4)
|
||||
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
|
||||
// from address for signature
|
||||
address.encode_to(&mut encoded_inner);
|
||||
// the signature
|
||||
signature.encode_to(&mut encoded_inner);
|
||||
// attach custom extra params
|
||||
self.additional_and_extra_params
|
||||
.encode_extra_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
|
||||
};
|
||||
|
||||
Value::unnamed_variant(self.pallet_name, [call])
|
||||
// Return an extrinsic ready to be submitted.
|
||||
Transaction::from_bytes(extrinsic)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CallData: EncodeAsFields> TxPayload for Payload<CallData> {
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
|
||||
let call = pallet
|
||||
.call_variant_by_name(&self.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
|
||||
|
||||
let pallet_index = pallet.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)
|
||||
.expect("The fields are valid types from the metadata, qed;");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validation_details(&self) -> Option<ValidationDetails<'_>> {
|
||||
self.validation_hash.map(|hash| ValidationDetails {
|
||||
pallet_name: &self.pallet_name,
|
||||
call_name: &self.call_name,
|
||||
hash,
|
||||
})
|
||||
}
|
||||
/// 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()`] to get the hash.
|
||||
pub struct Transaction<T> {
|
||||
encoded: Encoded,
|
||||
marker: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Construct a transaction at runtime; essentially an alias to [`Payload::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 {
|
||||
Payload::new(pallet_name, call_name, call_data.into())
|
||||
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 configured hasher.
|
||||
pub fn hash(&self) -> T::Hash {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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::error::MetadataError;
|
||||
use crate::metadata::Metadata;
|
||||
use crate::Error;
|
||||
use alloc::borrow::{Cow, ToOwned};
|
||||
use alloc::string::String;
|
||||
|
||||
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 PayloadT {
|
||||
/// Encode call data to the provided output.
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
|
||||
/// Encode call data and return the output. This is a convenience
|
||||
/// wrapper around [`PayloadT::encode_call_data_to`].
|
||||
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 Payload<CallData> {
|
||||
pallet_name: Cow<'static, str>,
|
||||
call_name: Cow<'static, str>,
|
||||
call_data: CallData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
/// The type of a payload typically used for dynamic transaction payloads.
|
||||
pub type DynamicPayload = Payload<Composite<()>>;
|
||||
|
||||
impl<CallData> Payload<CallData> {
|
||||
/// Create a new [`Payload`].
|
||||
pub fn new(
|
||||
pallet_name: impl Into<String>,
|
||||
call_name: impl Into<String>,
|
||||
call_data: CallData,
|
||||
) -> Self {
|
||||
Payload {
|
||||
pallet_name: Cow::Owned(pallet_name.into()),
|
||||
call_name: Cow::Owned(call_name.into()),
|
||||
call_data,
|
||||
validation_hash: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Payload`] 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 {
|
||||
Payload {
|
||||
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 Payload<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> PayloadT for Payload<CallData> {
|
||||
fn encode_call_data_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let pallet = metadata.pallet_by_name_err(&self.pallet_name)?;
|
||||
let call = pallet
|
||||
.call_variant_by_name(&self.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound((*self.call_name).to_owned()))?;
|
||||
|
||||
let pallet_index = pallet.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)
|
||||
.expect("The fields are valid types from the metadata, qed;");
|
||||
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 [`Payload::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 {
|
||||
Payload::new(pallet_name, call_name, call_data.into())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
@@ -24,14 +24,11 @@ pub use account_id::AccountId32;
|
||||
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;
|
||||
|
||||
// Used in codegen
|
||||
#[doc(hidden)]
|
||||
pub use primitive_types::{H160, H256, H512};
|
||||
|
||||
/// 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)]
|
||||
@@ -74,7 +71,7 @@ unsafe impl<T> Sync for PhantomDataSendSync<T> {}
|
||||
/// as `BTreeMap` which allows us to easily swap the two during codegen.
|
||||
pub type KeyedVec<K, V> = Vec<(K, V)>;
|
||||
|
||||
/// A unit marker struct signalling that some property is true
|
||||
/// A unit marker struct.
|
||||
pub struct Yes;
|
||||
|
||||
/// A quick helper to encode some bytes to hex.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
|
||||
Generated
+334
-221
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,11 @@ use futures::FutureExt;
|
||||
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
use subxt::ext::codec::{Decode, Encode};
|
||||
use subxt::tx::SubmittableExtrinsic;
|
||||
use subxt::tx::TxPayload;
|
||||
use subxt::utils::{AccountId32, MultiSignature};
|
||||
use subxt::config::DefaultExtrinsicParamsBuilder;
|
||||
use subxt::ext::codec::{Decode, Encode};
|
||||
use subxt::tx::PayloadT as _;
|
||||
use subxt::tx::SubmittableExtrinsic;
|
||||
use subxt::utils::{AccountId32, MultiSignature};
|
||||
|
||||
use crate::services::{extension_signature_for_extrinsic, get_accounts, polkadot, Account};
|
||||
use web_sys::HtmlInputElement;
|
||||
@@ -137,41 +137,54 @@ impl Component for SigningExamplesComponent {
|
||||
|
||||
let api = self.online_client.as_ref().unwrap().clone();
|
||||
|
||||
ctx.link()
|
||||
.send_future(
|
||||
async move {
|
||||
let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else {
|
||||
return Message::Error(anyhow!("Fetching account nonce failed"));
|
||||
};
|
||||
ctx.link().send_future(async move {
|
||||
let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else {
|
||||
return Message::Error(anyhow!("Fetching account nonce failed"));
|
||||
};
|
||||
|
||||
let Ok(call_data) = api.tx().call_data(&remark_call) else {
|
||||
return Message::Error(anyhow!("could not encode call data"));
|
||||
};
|
||||
let Ok(call_data) = api.tx().call_data(&remark_call) else {
|
||||
return Message::Error(anyhow!("could not encode call data"));
|
||||
};
|
||||
|
||||
let Ok(signature) = extension_signature_for_extrinsic(&call_data, &api, account_nonce, account_source, account_address).await else {
|
||||
return Message::Error(anyhow!("Signing via extension failed"));
|
||||
};
|
||||
let Ok(signature) = extension_signature_for_extrinsic(
|
||||
&call_data,
|
||||
&api,
|
||||
account_nonce,
|
||||
account_source,
|
||||
account_address,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return Message::Error(anyhow!("Signing via extension failed"));
|
||||
};
|
||||
|
||||
let Ok(multi_signature) = MultiSignature::decode(&mut &signature[..]) else {
|
||||
return Message::Error(anyhow!("MultiSignature Decoding"));
|
||||
};
|
||||
let Ok(multi_signature) = MultiSignature::decode(&mut &signature[..])
|
||||
else {
|
||||
return Message::Error(anyhow!("MultiSignature Decoding"));
|
||||
};
|
||||
|
||||
let params = DefaultExtrinsicParamsBuilder::new().nonce(account_nonce).build();
|
||||
let Ok(partial_signed) = api.tx().create_partial_signed_offline(&remark_call, params) else {
|
||||
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
|
||||
};
|
||||
let params = DefaultExtrinsicParamsBuilder::new()
|
||||
.nonce(account_nonce)
|
||||
.build();
|
||||
let Ok(partial_signed) =
|
||||
api.tx().create_partial_signed_offline(&remark_call, params)
|
||||
else {
|
||||
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
|
||||
};
|
||||
|
||||
// Apply the signature
|
||||
let signed_extrinsic = partial_signed.sign_with_address_and_signature(&account_id.into(), &multi_signature);
|
||||
// Apply the signature
|
||||
let signed_extrinsic = partial_signed
|
||||
.sign_with_address_and_signature(&account_id.into(), &multi_signature);
|
||||
|
||||
// check the TX validity (to debug in the js console if the extrinsic would work)
|
||||
let dry_res = signed_extrinsic.validate().await;
|
||||
web_sys::console::log_1(&format!("Validation Result: {:?}", dry_res).into());
|
||||
|
||||
// return the signature and signed extrinsic
|
||||
Message::ReceivedSignature(multi_signature, signed_extrinsic)
|
||||
}
|
||||
// check the TX validity (to debug in the js console if the extrinsic would work)
|
||||
let dry_res = signed_extrinsic.validate().await;
|
||||
web_sys::console::log_1(
|
||||
&format!("Validation Result: {:?}", dry_res).into(),
|
||||
);
|
||||
|
||||
// return the signature and signed extrinsic
|
||||
Message::ReceivedSignature(multi_signature, signed_extrinsic)
|
||||
});
|
||||
}
|
||||
}
|
||||
Message::ReceivedSignature(signature, signed_extrinsic) => {
|
||||
@@ -192,7 +205,9 @@ impl Component for SigningExamplesComponent {
|
||||
..
|
||||
} = &mut self.stage
|
||||
{
|
||||
let SubmittingStage::Initial { signed_extrinsic } = std::mem::replace(submitting_stage, SubmittingStage::Submitting) else {
|
||||
let SubmittingStage::Initial { signed_extrinsic } =
|
||||
std::mem::replace(submitting_stage, SubmittingStage::Submitting)
|
||||
else {
|
||||
panic!("unreachable")
|
||||
};
|
||||
|
||||
|
||||
@@ -130,8 +130,8 @@ pub async fn extension_signature_for_extrinsic(
|
||||
) -> Result<Vec<u8>, anyhow::Error> {
|
||||
let genesis_hash = encode_then_hex(&api.genesis_hash());
|
||||
// These numbers aren't SCALE encoded; their bytes are just converted to hex:
|
||||
let spec_version = to_hex(&api.runtime_version().spec_version().to_be_bytes());
|
||||
let transaction_version = to_hex(&api.runtime_version().transaction_version().to_be_bytes());
|
||||
let spec_version = to_hex(&api.runtime_version().spec_version.to_be_bytes());
|
||||
let transaction_version = to_hex(&api.runtime_version().transaction_version.to_be_bytes());
|
||||
let nonce = to_hex(&account_nonce.to_be_bytes());
|
||||
// If you construct a mortal transaction, then this block hash needs to correspond
|
||||
// to the block number passed to `Era::mortal()`.
|
||||
|
||||
+1
-1
@@ -283,7 +283,7 @@ mod subxt_compat {
|
||||
use super::*;
|
||||
|
||||
use subxt_core::config::Config;
|
||||
use subxt_core::tx::Signer as SignerT;
|
||||
use subxt_core::tx::signer::Signer as SignerT;
|
||||
use subxt_core::utils::{AccountId32, MultiAddress, MultiSignature};
|
||||
|
||||
impl From<Signature> for MultiSignature {
|
||||
|
||||
@@ -259,7 +259,7 @@ mod subxt_compat {
|
||||
use super::*;
|
||||
|
||||
use subxt_core::{
|
||||
tx::Signer as SignerT,
|
||||
tx::signer::Signer as SignerT,
|
||||
utils::{AccountId32, MultiAddress, MultiSignature},
|
||||
Config,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
|
||||
// 2. A runtime version (system_version constant on a Substrate node has these):
|
||||
let runtime_version = subxt::client::RuntimeVersion::new(9370, 20);
|
||||
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):
|
||||
|
||||
@@ -62,7 +62,7 @@ impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
|
||||
// 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(),
|
||||
genesis_hash: client.genesis_hash,
|
||||
tip: params.tip,
|
||||
foo: params.foo,
|
||||
})
|
||||
|
||||
@@ -22,9 +22,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_query)
|
||||
.fetch_raw(subxt_core::storage::get_address_bytes(&storage_query, &api.metadata()).unwrap())
|
||||
.await?;
|
||||
|
||||
println!("Alice has free balance: {}", result.unwrap().data.free);
|
||||
let v = hex::encode(result.unwrap());
|
||||
println!("Alice: {v}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -181,16 +181,20 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for LegacyBackend<T> {
|
||||
|
||||
async fn current_runtime_version(&self) -> Result<RuntimeVersion, Error> {
|
||||
let details = self.methods.state_get_runtime_version(None).await?;
|
||||
Ok(RuntimeVersion::new(
|
||||
details.spec_version,
|
||||
details.transaction_version,
|
||||
))
|
||||
Ok(RuntimeVersion {
|
||||
spec_version: details.spec_version,
|
||||
transaction_version: details.transaction_version,
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_runtime_version(&self) -> Result<StreamOfResults<RuntimeVersion>, Error> {
|
||||
let sub = self.methods.state_subscribe_runtime_version().await?;
|
||||
let sub =
|
||||
sub.map(|r| r.map(|v| RuntimeVersion::new(v.spec_version, v.transaction_version)));
|
||||
let sub = sub.map(|r| {
|
||||
r.map(|v| RuntimeVersion {
|
||||
spec_version: v.spec_version,
|
||||
transaction_version: v.transaction_version,
|
||||
})
|
||||
});
|
||||
Ok(StreamOf(Box::pin(sub)))
|
||||
}
|
||||
|
||||
|
||||
@@ -416,7 +416,10 @@ impl<T: Config + Send + Sync + 'static> Backend<T> for UnstableBackend<T> {
|
||||
RuntimeEvent::Valid(ev) => ev,
|
||||
};
|
||||
|
||||
let runtime_version = RuntimeVersion::new(runtime_details.spec.spec_version, runtime_details.spec.transaction_version);
|
||||
let runtime_version = RuntimeVersion {
|
||||
spec_version: runtime_details.spec.spec_version,
|
||||
transaction_version: runtime_details.spec.transaction_version
|
||||
};
|
||||
std::future::ready(Some(Ok(runtime_version)))
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use crate::{
|
||||
backend::BlockRef,
|
||||
blocks::{extrinsic_types::ExtrinsicPartTypeIds, Extrinsics},
|
||||
blocks::Extrinsics,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{Config, Header},
|
||||
error::{BlockError, DecodeError, Error},
|
||||
@@ -79,7 +79,6 @@ where
|
||||
|
||||
/// Fetch and return the extrinsics in the block body.
|
||||
pub async fn extrinsics(&self) -> Result<Extrinsics<T, C>, Error> {
|
||||
let ids = ExtrinsicPartTypeIds::new(&self.client.metadata())?;
|
||||
let block_hash = self.header.hash();
|
||||
let Some(extrinsics) = self.client.backend().block_body(block_hash).await? else {
|
||||
return Err(BlockError::not_found(block_hash).into());
|
||||
@@ -89,9 +88,8 @@ where
|
||||
self.client.clone(),
|
||||
extrinsics,
|
||||
self.cached_events.clone(),
|
||||
ids,
|
||||
block_hash,
|
||||
))
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Work with storage.
|
||||
|
||||
+136
-800
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ pub use block_types::Block;
|
||||
pub use blocks_client::BlocksClient;
|
||||
pub use extrinsic_types::{
|
||||
ExtrinsicDetails, ExtrinsicEvents, ExtrinsicSignedExtension, ExtrinsicSignedExtensions,
|
||||
Extrinsics, StaticExtrinsic,
|
||||
Extrinsics, FoundExtrinsic, StaticExtrinsic,
|
||||
};
|
||||
|
||||
// We get account nonce info in tx_client, too, so re-use the logic:
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! All valid runtime calls implement [`crate::runtime_api::RuntimeApiPayload`], a trait which
|
||||
//! All valid runtime calls implement [`crate::runtime_api::PayloadT`], a trait which
|
||||
//! describes how to encode the runtime call arguments and what return type to decode from the
|
||||
//! response.
|
||||
//!
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
//! - `runtime::storage().foo().bar_iter3(u8, bool, u16)`: iterate over all of the entries in the "bar" map under
|
||||
//! a given `u8`, `bool` and `u16` value.
|
||||
//!
|
||||
//! All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing
|
||||
//! All valid storage queries implement [`crate::storage::AddressT`]. As well as describing
|
||||
//! how to build a valid storage query, this trait also has some associated types that determine the
|
||||
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
|
||||
//! over storage entries using it).
|
||||
@@ -124,7 +124,7 @@
|
||||
//!
|
||||
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
|
||||
//! [`crate::storage::Storage::fetch_raw_keys`]. Both of these take raw bytes as arguments, which can be
|
||||
//! obtained from a [`crate::storage::StorageAddress`] by using
|
||||
//! obtained from a [`crate::storage::AddressT`] by using
|
||||
//! [`crate::storage::StorageClient::address_bytes()`] or
|
||||
//! [`crate::storage::StorageClient::address_root_bytes()`].
|
||||
//!
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
//! represents any type of data that can be SCALE encoded or decoded. It can be serialized,
|
||||
//! deserialized and parsed from/to strings.
|
||||
//!
|
||||
//! A valid transaction payload is just something that implements the [`crate::tx::TxPayload`] trait;
|
||||
//! A valid transaction payload is just something that implements the [`crate::tx::PayloadT`] trait;
|
||||
//! you can implement this trait on your own custom types if the built-in ones are not suitable for
|
||||
//! your needs.
|
||||
//!
|
||||
|
||||
@@ -17,13 +17,20 @@ use subxt_core::client::{ClientState, RuntimeVersion};
|
||||
pub trait OfflineClientT<T: Config>: Clone + Send + Sync + 'static {
|
||||
/// Return the provided [`Metadata`].
|
||||
fn metadata(&self) -> Metadata;
|
||||
|
||||
/// Return the provided genesis hash.
|
||||
fn genesis_hash(&self) -> T::Hash;
|
||||
|
||||
/// Return the provided [`RuntimeVersion`].
|
||||
fn runtime_version(&self) -> RuntimeVersion;
|
||||
|
||||
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
|
||||
fn client_state(&self) -> ClientState<T> {
|
||||
ClientState::new(self.genesis_hash(), self.runtime_version(), self.metadata())
|
||||
ClientState {
|
||||
genesis_hash: self.genesis_hash(),
|
||||
runtime_version: self.runtime_version(),
|
||||
metadata: self.metadata(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Work with transactions.
|
||||
@@ -77,37 +84,30 @@ impl<T: Config> OfflineClient<T> {
|
||||
runtime_version: RuntimeVersion,
|
||||
metadata: impl Into<Metadata>,
|
||||
) -> OfflineClient<T> {
|
||||
let metadata = metadata.into();
|
||||
|
||||
OfflineClient {
|
||||
inner: Arc::new(ClientState::new(
|
||||
inner: Arc::new(ClientState {
|
||||
genesis_hash,
|
||||
runtime_version,
|
||||
metadata.into(),
|
||||
)),
|
||||
metadata,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the genesis hash.
|
||||
pub fn genesis_hash(&self) -> T::Hash {
|
||||
self.inner.genesis_hash()
|
||||
self.inner.genesis_hash
|
||||
}
|
||||
|
||||
/// Return the runtime version.
|
||||
pub fn runtime_version(&self) -> RuntimeVersion {
|
||||
self.inner.runtime_version()
|
||||
self.inner.runtime_version
|
||||
}
|
||||
|
||||
/// Return the [`Metadata`] used in this client.
|
||||
pub fn metadata(&self) -> Metadata {
|
||||
self.inner.metadata()
|
||||
}
|
||||
|
||||
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
|
||||
pub fn client_state(&self) -> ClientState<T> {
|
||||
ClientState::new(
|
||||
self.inner.genesis_hash(),
|
||||
self.inner.runtime_version(),
|
||||
self.inner.metadata(),
|
||||
)
|
||||
self.inner.metadata.clone()
|
||||
}
|
||||
|
||||
// Just a copy of the most important trait methods so that people
|
||||
@@ -149,9 +149,6 @@ impl<T: Config> OfflineClientT<T> for OfflineClient<T> {
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.metadata()
|
||||
}
|
||||
fn client_state(&self) -> ClientState<T> {
|
||||
self.client_state()
|
||||
}
|
||||
}
|
||||
|
||||
// For ergonomics; cloning a client is deliberately fairly cheap (via Arc),
|
||||
|
||||
@@ -228,7 +228,7 @@ impl<T: Config> OnlineClient<T> {
|
||||
/// let mut update_stream = updater.runtime_updates().await.unwrap();
|
||||
///
|
||||
/// while let Some(Ok(update)) = update_stream.next().await {
|
||||
/// let version = update.runtime_version().spec_version();
|
||||
/// let version = update.runtime_version().spec_version;
|
||||
///
|
||||
/// match updater.apply_update(update) {
|
||||
/// Ok(()) => {
|
||||
@@ -286,16 +286,6 @@ impl<T: Config> OnlineClient<T> {
|
||||
inner.runtime_version
|
||||
}
|
||||
|
||||
/// Return the [subxt_core::client::ClientState] (metadata, runtime version and genesis hash).
|
||||
pub fn client_state(&self) -> ClientState<T> {
|
||||
let inner = self.inner.read().expect("shouldn't be poisoned");
|
||||
ClientState::new(
|
||||
inner.genesis_hash,
|
||||
inner.runtime_version,
|
||||
inner.metadata.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Change the [`RuntimeVersion`] used in this client.
|
||||
///
|
||||
/// # Warning
|
||||
@@ -371,9 +361,14 @@ impl<T: Config> OfflineClientT<T> for OnlineClient<T> {
|
||||
fn runtime_version(&self) -> RuntimeVersion {
|
||||
self.runtime_version()
|
||||
}
|
||||
|
||||
// This is provided by default, but we can optimise here and only lock once:
|
||||
fn client_state(&self) -> ClientState<T> {
|
||||
self.client_state()
|
||||
let inner = self.inner.read().expect("shouldn't be poisoned");
|
||||
ClientState {
|
||||
genesis_hash: inner.genesis_hash,
|
||||
runtime_version: inner.runtime_version,
|
||||
metadata: inner.metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +546,7 @@ async fn wait_runtime_upgrade_in_finalized_block<T: Config>(
|
||||
|
||||
// We are waiting for the chain to have the same spec version
|
||||
// as sent out via the runtime subscription.
|
||||
if spec_version == runtime_version.spec_version() {
|
||||
if spec_version == runtime_version.spec_version {
|
||||
break block_ref;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::ConstantAddress;
|
||||
use crate::{client::OfflineClientT, error::Error, Config};
|
||||
use derive_where::derive_where;
|
||||
use subxt_core::constants::address::AddressT;
|
||||
|
||||
/// A client for accessing constants.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -28,19 +28,16 @@ impl<T: Config, Client: OfflineClientT<T>> ConstantsClient<T, Client> {
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or constant in question do not exist at all).
|
||||
pub fn validate<Address: ConstantAddress>(&self, address: &Address) -> Result<(), Error> {
|
||||
pub fn validate<Address: AddressT>(&self, address: &Address) -> Result<(), Error> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::validate_constant(&metadata, address).map_err(Error::from)
|
||||
subxt_core::constants::validate(address, &metadata).map_err(Error::from)
|
||||
}
|
||||
|
||||
/// Access the constant at the address given, returning the type defined by this address.
|
||||
/// This is probably used with addresses given from static codegen, although you can manually
|
||||
/// construct your own, too.
|
||||
pub fn at<Address: ConstantAddress>(
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Address::Target, Error> {
|
||||
pub fn at<Address: AddressT>(&self, address: &Address) -> Result<Address::Target, Error> {
|
||||
let metadata = self.client.metadata();
|
||||
subxt_core::constants::get_constant(&metadata, address).map_err(Error::from)
|
||||
subxt_core::constants::get(address, &metadata).map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
mod constants_client;
|
||||
|
||||
pub use constants_client::ConstantsClient;
|
||||
pub use subxt_core::constants::{dynamic, Address, ConstantAddress, DynamicAddress};
|
||||
pub use subxt_core::constants::address::{dynamic, Address, AddressT, DynamicAddress};
|
||||
|
||||
@@ -2,10 +2,7 @@ use crate::client::OfflineClientT;
|
||||
use crate::{Config, Error};
|
||||
use derive_where::derive_where;
|
||||
|
||||
use subxt_core::custom_values::{
|
||||
get_custom_value, get_custom_value_bytes, validate_custom_value, CustomValueAddress,
|
||||
};
|
||||
use subxt_core::utils::Yes;
|
||||
use subxt_core::custom_values::address::{AddressT, Yes};
|
||||
|
||||
/// A client for accessing custom values stored in the metadata.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -27,29 +24,26 @@ impl<T, Client> CustomValuesClient<T, Client> {
|
||||
impl<T: Config, Client: OfflineClientT<T>> CustomValuesClient<T, Client> {
|
||||
/// 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 at<Address: CustomValueAddress<IsDecodable = Yes> + ?Sized>(
|
||||
pub fn at<Address: AddressT<IsDecodable = Yes> + ?Sized>(
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Address::Target, Error> {
|
||||
get_custom_value(&self.client.metadata(), address).map_err(Into::into)
|
||||
subxt_core::custom_values::get(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Access the bytes of a custom value by the address it is registered under.
|
||||
pub fn bytes_at<Address: CustomValueAddress + ?Sized>(
|
||||
pub fn bytes_at<Address: AddressT + ?Sized>(
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
get_custom_value_bytes(&self.client.metadata(), address).map_err(Into::into)
|
||||
subxt_core::custom_values::get_bytes(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// 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<Address: CustomValueAddress + ?Sized>(
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<(), Error> {
|
||||
validate_custom_value(&self.client.metadata(), address).map_err(Into::into)
|
||||
pub fn validate<Address: AddressT + ?Sized>(&self, address: &Address) -> Result<(), Error> {
|
||||
subxt_core::custom_values::validate(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,14 +107,17 @@ mod tests {
|
||||
};
|
||||
|
||||
let metadata: subxt_metadata::Metadata = frame_metadata.try_into().unwrap();
|
||||
Metadata::new(metadata)
|
||||
Metadata::from(metadata)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decoding() {
|
||||
let client = OfflineClient::<SubstrateConfig>::new(
|
||||
Default::default(),
|
||||
RuntimeVersion::new(0, 0),
|
||||
RuntimeVersion {
|
||||
spec_version: 0,
|
||||
transaction_version: 0,
|
||||
},
|
||||
mock_metadata(),
|
||||
);
|
||||
let custom_value_client = CustomValuesClient::new(client);
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
mod custom_values_client;
|
||||
|
||||
pub use custom_values_client::CustomValuesClient;
|
||||
pub use subxt_core::custom_values::{CustomValueAddress, StaticAddress};
|
||||
pub use subxt_core::custom_values::address::{AddressT, StaticAddress, Yes};
|
||||
|
||||
+21
-8
@@ -6,6 +6,8 @@
|
||||
|
||||
mod dispatch_error;
|
||||
|
||||
use subxt_core::error::{BlockError as CoreBlockError, Error as CoreError};
|
||||
|
||||
crate::macros::cfg_unstable_light_client! {
|
||||
pub use subxt_lightclient::LightClientError;
|
||||
}
|
||||
@@ -80,15 +82,16 @@ pub enum Error {
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl From<subxt_core::Error> for Error {
|
||||
fn from(value: subxt_core::Error) -> Self {
|
||||
impl From<CoreError> for Error {
|
||||
fn from(value: CoreError) -> Self {
|
||||
match value {
|
||||
subxt_core::Error::Codec(e) => Error::Codec(e),
|
||||
subxt_core::Error::Metadata(e) => Error::Metadata(e),
|
||||
subxt_core::Error::StorageAddress(e) => Error::StorageAddress(e),
|
||||
subxt_core::Error::Decode(e) => Error::Decode(e),
|
||||
subxt_core::Error::Encode(e) => Error::Encode(e),
|
||||
subxt_core::Error::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
|
||||
CoreError::Codec(e) => Error::Codec(e),
|
||||
CoreError::Metadata(e) => Error::Metadata(e),
|
||||
CoreError::StorageAddress(e) => Error::StorageAddress(e),
|
||||
CoreError::Decode(e) => Error::Decode(e),
|
||||
CoreError::Encode(e) => Error::Encode(e),
|
||||
CoreError::ExtrinsicParams(e) => Error::ExtrinsicParams(e),
|
||||
CoreError::Block(e) => Error::Block(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,6 +178,16 @@ pub enum BlockError {
|
||||
DecodingError(codec::Error),
|
||||
}
|
||||
|
||||
impl From<CoreBlockError> for BlockError {
|
||||
fn from(value: CoreBlockError) -> Self {
|
||||
match value {
|
||||
CoreBlockError::MissingType => BlockError::MissingType,
|
||||
CoreBlockError::UnsupportedVersion(n) => BlockError::UnsupportedVersion(n),
|
||||
CoreBlockError::DecodingError(e) => BlockError::DecodingError(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockError {
|
||||
/// Produce an error that a block with the given hash cannot be found.
|
||||
pub fn not_found(hash: impl AsRef<[u8]>) -> BlockError {
|
||||
|
||||
@@ -64,7 +64,7 @@ where
|
||||
};
|
||||
|
||||
let event_bytes = get_event_bytes(client.backend(), block_ref.hash()).await?;
|
||||
Ok(Events::decode_from(client.metadata(), event_bytes))
|
||||
Ok(Events::decode_from(event_bytes, client.metadata()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,5 @@ where
|
||||
C: OnlineClientT<T>,
|
||||
{
|
||||
let event_bytes = events_client::get_event_bytes(client.backend(), block_hash).await?;
|
||||
Ok(Events::<T>::decode_from(metadata, event_bytes))
|
||||
Ok(Events::<T>::decode_from(event_bytes, metadata))
|
||||
}
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ pub mod config {
|
||||
|
||||
/// Types representing the metadata obtained from a node.
|
||||
pub mod metadata {
|
||||
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata, MetadataExt};
|
||||
pub use subxt_core::metadata::{DecodeWithMetadata, EncodeWithMetadata, Metadata};
|
||||
// Expose metadata types under a sub module in case somebody needs to reference them:
|
||||
pub use subxt_metadata as types;
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ mod runtime_types;
|
||||
|
||||
pub use runtime_client::RuntimeApiClient;
|
||||
pub use runtime_types::RuntimeApi;
|
||||
pub use subxt_core::runtime_api::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
|
||||
pub use subxt_core::runtime_api::payload::{dynamic, DynamicPayload, Payload, PayloadT};
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::PayloadT;
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef},
|
||||
client::OnlineClientT,
|
||||
error::{Error, MetadataError},
|
||||
metadata::DecodeWithMetadata,
|
||||
error::Error,
|
||||
Config,
|
||||
};
|
||||
use codec::Decode;
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
use super::RuntimeApiPayload;
|
||||
|
||||
/// Execute runtime API calls.
|
||||
#[derive_where(Clone; Client)]
|
||||
pub struct RuntimeApi<T: Config, Client> {
|
||||
@@ -39,6 +37,14 @@ where
|
||||
T: Config,
|
||||
Client: OnlineClientT<T>,
|
||||
{
|
||||
/// 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<Call: PayloadT>(&self, payload: &Call) -> Result<(), Error> {
|
||||
subxt_core::runtime_api::validate(payload, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Execute a raw runtime API call.
|
||||
pub fn call_raw<'a, Res: Decode>(
|
||||
&self,
|
||||
@@ -59,7 +65,7 @@ where
|
||||
}
|
||||
|
||||
/// Execute a runtime API call.
|
||||
pub fn call<Call: RuntimeApiPayload>(
|
||||
pub fn call<Call: PayloadT>(
|
||||
&self,
|
||||
payload: Call,
|
||||
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
|
||||
@@ -70,39 +76,21 @@ where
|
||||
async move {
|
||||
let metadata = client.metadata();
|
||||
|
||||
let api_trait = metadata.runtime_api_trait_by_name_err(payload.trait_name())?;
|
||||
let api_method = api_trait
|
||||
.method_by_name(payload.method_name())
|
||||
.ok_or_else(|| {
|
||||
MetadataError::RuntimeMethodNotFound(payload.method_name().to_owned())
|
||||
})?;
|
||||
|
||||
// Validate the runtime API payload hash against the compile hash from codegen.
|
||||
if let Some(static_hash) = payload.validation_hash() {
|
||||
let Some(runtime_hash) = api_trait.method_hash(payload.method_name()) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
if static_hash != runtime_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
}
|
||||
subxt_core::runtime_api::validate(&payload, &metadata)?;
|
||||
|
||||
// Encode the arguments of the runtime call.
|
||||
// For static payloads (codegen) this is pass-through, bytes are not altered.
|
||||
// For dynamic payloads this relies on `scale_value::encode_as_fields_to`.
|
||||
let params = payload.encode_args(&metadata)?;
|
||||
let call_name = format!("{}_{}", payload.trait_name(), payload.method_name());
|
||||
let call_name = subxt_core::runtime_api::call_name(&payload);
|
||||
let call_args = subxt_core::runtime_api::call_args(&payload, &metadata)?;
|
||||
|
||||
// Make the call.
|
||||
let bytes = client
|
||||
.backend()
|
||||
.call(&call_name, Some(params.as_slice()), block_hash)
|
||||
.call(&call_name, Some(call_args.as_slice()), block_hash)
|
||||
.await?;
|
||||
|
||||
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut &bytes[..],
|
||||
api_method.output_ty(),
|
||||
&metadata,
|
||||
)?;
|
||||
// Decode the response.
|
||||
let value = subxt_core::runtime_api::decode_value(&mut &*bytes, &payload, &metadata)?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,7 @@ mod storage_client;
|
||||
mod storage_type;
|
||||
|
||||
pub use storage_client::StorageClient;
|
||||
|
||||
pub use storage_type::{Storage, StorageKeyValuePair};
|
||||
|
||||
/// Types representing an address which describes where a storage
|
||||
/// entry lives and how to properly decode it.
|
||||
pub mod address {
|
||||
pub use subxt_core::storage::address::{
|
||||
dynamic, Address, DynamicAddress, StaticStorageKey, StorageAddress, StorageKey,
|
||||
};
|
||||
}
|
||||
|
||||
pub use subxt_core::storage::StorageKey;
|
||||
|
||||
// For consistency with other modules, also expose
|
||||
// the basic address stuff at the root of the module.
|
||||
pub use address::{dynamic, Address, DynamicAddress, StorageAddress};
|
||||
pub use subxt_core::storage::address::{
|
||||
dynamic, Address, AddressT, DynamicAddress, StaticStorageKey, StorageKey,
|
||||
};
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use super::{
|
||||
storage_type::{validate_storage_address, Storage},
|
||||
StorageAddress,
|
||||
};
|
||||
use super::storage_type::Storage;
|
||||
use crate::{
|
||||
backend::BlockRef,
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
@@ -14,6 +11,7 @@ use crate::{
|
||||
};
|
||||
use derive_where::derive_where;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use subxt_core::storage::address::AddressT;
|
||||
|
||||
/// Query the runtime storage.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -41,29 +39,23 @@ where
|
||||
/// if the address is valid (or if it's not possible to check since the address has no validation hash).
|
||||
/// Return an error if the address was not valid or something went wrong trying to validate it (ie
|
||||
/// the pallet or storage entry in question do not exist at all).
|
||||
pub fn validate<Address: StorageAddress>(&self, address: &Address) -> Result<(), Error> {
|
||||
let metadata = self.client.metadata();
|
||||
let pallet_metadata = metadata.pallet_by_name_err(address.pallet_name())?;
|
||||
validate_storage_address(address, pallet_metadata)
|
||||
pub fn validate<Address: AddressT>(&self, address: &Address) -> Result<(), Error> {
|
||||
subxt_core::storage::validate(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve the entries at the root of the associated address.
|
||||
pub fn address_root_bytes<Address: StorageAddress>(&self, address: &Address) -> Vec<u8> {
|
||||
subxt_core::storage::utils::storage_address_root_bytes(address)
|
||||
pub fn address_root_bytes<Address: AddressT>(&self, address: &Address) -> Vec<u8> {
|
||||
subxt_core::storage::get_address_root_bytes(address)
|
||||
}
|
||||
|
||||
/// Convert some storage address into the raw bytes that would be submitted to the node in order
|
||||
/// to retrieve an entry. This fails if [`StorageAddress::append_entry_bytes`] does; in the built-in
|
||||
/// to retrieve an entry. This fails if [`AddressT::append_entry_bytes`] does; in the built-in
|
||||
/// implementation this would be if the pallet and storage entry being asked for is not available on the
|
||||
/// node you're communicating with, or if the metadata is missing some type information (which should not
|
||||
/// happen).
|
||||
pub fn address_bytes<Address: StorageAddress>(
|
||||
&self,
|
||||
address: &Address,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::storage::utils::storage_address_bytes(address, &self.client.metadata())
|
||||
.map_err(Into::into)
|
||||
pub fn address_bytes<Address: AddressT>(&self, address: &Address) -> Result<Vec<u8>, Error> {
|
||||
subxt_core::storage::get_address_bytes(address, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use subxt_core::storage::address::{StorageAddress, StorageHashers, StorageKey};
|
||||
use subxt_core::utils::Yes;
|
||||
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef},
|
||||
client::OnlineClientT,
|
||||
error::{Error, MetadataError, StorageAddressError},
|
||||
metadata::{DecodeWithMetadata, Metadata},
|
||||
metadata::DecodeWithMetadata,
|
||||
Config,
|
||||
};
|
||||
use codec::Decode;
|
||||
use derive_where::derive_where;
|
||||
use futures::StreamExt;
|
||||
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
use subxt_metadata::{PalletMetadata, StorageEntryMetadata, StorageEntryType};
|
||||
use subxt_core::storage::address::{AddressT, StorageHashers, StorageKey};
|
||||
use subxt_core::utils::Yes;
|
||||
|
||||
/// This is returned from a couple of storage functions.
|
||||
pub use crate::backend::StreamOfResults;
|
||||
@@ -119,26 +115,22 @@ where
|
||||
address: &'address Address,
|
||||
) -> impl Future<Output = Result<Option<Address::Target>, Error>> + 'address
|
||||
where
|
||||
Address: StorageAddress<IsFetchable = Yes> + 'address,
|
||||
Address: AddressT<IsFetchable = Yes> + 'address,
|
||||
{
|
||||
let client = self.clone();
|
||||
async move {
|
||||
let metadata = client.client.metadata();
|
||||
let (pallet, entry) =
|
||||
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
|
||||
|
||||
// Metadata validation checks whether the static address given
|
||||
// is likely to actually correspond to a real storage entry or not.
|
||||
// if not, it means static codegen doesn't line up with runtime
|
||||
// metadata.
|
||||
validate_storage_address(address, pallet)?;
|
||||
subxt_core::storage::validate(address, &metadata)?;
|
||||
|
||||
// Look up the return type ID to enable DecodeWithMetadata:
|
||||
let lookup_bytes =
|
||||
subxt_core::storage::utils::storage_address_bytes(address, &metadata)?;
|
||||
let lookup_bytes = subxt_core::storage::get_address_bytes(address, &metadata)?;
|
||||
if let Some(data) = client.fetch_raw(lookup_bytes).await? {
|
||||
let val =
|
||||
decode_storage_with_metadata::<Address::Target>(&mut &*data, &metadata, entry)?;
|
||||
let val = subxt_core::storage::decode_value(&mut &*data, address, &metadata)?;
|
||||
Ok(Some(val))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -152,24 +144,16 @@ where
|
||||
address: &'address Address,
|
||||
) -> impl Future<Output = Result<Address::Target, Error>> + 'address
|
||||
where
|
||||
Address: StorageAddress<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
|
||||
Address: AddressT<IsFetchable = Yes, IsDefaultable = Yes> + 'address,
|
||||
{
|
||||
let client = self.clone();
|
||||
async move {
|
||||
let pallet_name = address.pallet_name();
|
||||
let entry_name = address.entry_name();
|
||||
// Metadata validation happens via .fetch():
|
||||
if let Some(data) = client.fetch(address).await? {
|
||||
Ok(data)
|
||||
} else {
|
||||
let metadata = client.client.metadata();
|
||||
let (_pallet_metadata, storage_entry) =
|
||||
lookup_entry_details(pallet_name, entry_name, &metadata)?;
|
||||
|
||||
let return_ty_id = return_type_from_storage_entry_type(storage_entry.entry_type());
|
||||
let bytes = &mut storage_entry.default_bytes();
|
||||
|
||||
let val = Address::Target::decode_with_metadata(bytes, return_ty_id, &metadata)?;
|
||||
let val = subxt_core::storage::default_value(address, &metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
@@ -211,21 +195,24 @@ where
|
||||
address: Address,
|
||||
) -> impl Future<Output = Result<StreamOfResults<StorageKeyValuePair<Address>>, Error>> + 'static
|
||||
where
|
||||
Address: StorageAddress<IsIterable = Yes> + 'static,
|
||||
Address: AddressT<IsIterable = Yes> + 'static,
|
||||
Address::Keys: 'static + Sized,
|
||||
{
|
||||
let client = self.client.clone();
|
||||
let block_ref = self.block_ref.clone();
|
||||
async move {
|
||||
let metadata = client.metadata();
|
||||
let (pallet, entry) =
|
||||
lookup_entry_details(address.pallet_name(), address.entry_name(), &metadata)?;
|
||||
let (_pallet, entry) = subxt_core::storage::lookup_storage_entry_details(
|
||||
address.pallet_name(),
|
||||
address.entry_name(),
|
||||
&metadata,
|
||||
)?;
|
||||
|
||||
// Metadata validation checks whether the static address given
|
||||
// is likely to actually correspond to a real storage entry or not.
|
||||
// if not, it means static codegen doesn't line up with runtime
|
||||
// metadata.
|
||||
validate_storage_address(&address, pallet)?;
|
||||
subxt_core::storage::validate(&address, &metadata)?;
|
||||
|
||||
// Look up the return type for flexible decoding. Do this once here to avoid
|
||||
// potentially doing it every iteration if we used `decode_storage_with_metadata`
|
||||
@@ -236,8 +223,7 @@ where
|
||||
let hashers = StorageHashers::new(entry, metadata.types())?;
|
||||
|
||||
// The address bytes of this entry:
|
||||
let address_bytes =
|
||||
subxt_core::storage::utils::storage_address_bytes(&address, &metadata)?;
|
||||
let address_bytes = subxt_core::storage::get_address_bytes(&address, &metadata)?;
|
||||
let s = client
|
||||
.backend()
|
||||
.storage_fetch_descendant_values(address_bytes, block_ref.hash())
|
||||
@@ -327,7 +313,7 @@ fn strip_storage_address_root_bytes(address_bytes: &mut &[u8]) -> Result<(), Sto
|
||||
/// A pair of keys and values together with all the bytes that make up the storage address.
|
||||
/// `keys` is `None` if non-concat hashers are used. In this case the keys could not be extracted back from the key_bytes.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct StorageKeyValuePair<T: StorageAddress> {
|
||||
pub struct StorageKeyValuePair<T: AddressT> {
|
||||
/// The bytes that make up the address of the storage entry.
|
||||
pub key_bytes: Vec<u8>,
|
||||
/// The keys that can be used to construct the address of this storage entry.
|
||||
@@ -335,65 +321,3 @@ pub struct StorageKeyValuePair<T: StorageAddress> {
|
||||
/// The value of the storage entry.
|
||||
pub value: T::Target,
|
||||
}
|
||||
|
||||
/// Validate a storage address against the metadata.
|
||||
pub(crate) fn validate_storage_address<Address: StorageAddress>(
|
||||
address: &Address,
|
||||
pallet: PalletMetadata<'_>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(hash) = address.validation_hash() {
|
||||
validate_storage(pallet, address.entry_name(), hash)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return details about the given storage entry.
|
||||
fn lookup_entry_details<'a>(
|
||||
pallet_name: &str,
|
||||
entry_name: &str,
|
||||
metadata: &'a Metadata,
|
||||
) -> Result<(PalletMetadata<'a>, &'a StorageEntryMetadata), Error> {
|
||||
let pallet_metadata = metadata.pallet_by_name_err(pallet_name)?;
|
||||
let storage_metadata = pallet_metadata
|
||||
.storage()
|
||||
.ok_or_else(|| MetadataError::StorageNotFoundInPallet(pallet_name.to_owned()))?;
|
||||
let storage_entry = storage_metadata
|
||||
.entry_by_name(entry_name)
|
||||
.ok_or_else(|| MetadataError::StorageEntryNotFound(entry_name.to_owned()))?;
|
||||
Ok((pallet_metadata, storage_entry))
|
||||
}
|
||||
|
||||
/// Validate a storage entry against the metadata.
|
||||
fn validate_storage(
|
||||
pallet: PalletMetadata<'_>,
|
||||
storage_name: &str,
|
||||
hash: [u8; 32],
|
||||
) -> Result<(), Error> {
|
||||
let Some(expected_hash) = pallet.storage_hash(storage_name) else {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
};
|
||||
if expected_hash != hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch the return type out of a [`StorageEntryType`].
|
||||
fn return_type_from_storage_entry_type(entry: &StorageEntryType) -> u32 {
|
||||
match entry {
|
||||
StorageEntryType::Plain(ty) => *ty,
|
||||
StorageEntryType::Map { value_ty, .. } => *value_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given some bytes, a pallet and storage name, decode the response.
|
||||
fn decode_storage_with_metadata<T: DecodeWithMetadata>(
|
||||
bytes: &mut &[u8],
|
||||
metadata: &Metadata,
|
||||
storage_metadata: &StorageEntryMetadata,
|
||||
) -> Result<T, Error> {
|
||||
let ty = storage_metadata.entry_type();
|
||||
let return_ty = return_type_from_storage_entry_type(ty);
|
||||
let val = T::decode_with_metadata(bytes, return_ty, metadata)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! these utility methods complement the [`StorageAddress`] trait, but
|
||||
//! aren't things that should ever be overridden, and so don't exist on
|
||||
//! the trait itself.
|
||||
|
||||
use subxt_metadata::StorageHasher;
|
||||
|
||||
use super::StorageAddress;
|
||||
use crate::{error::Error, metadata::Metadata};
|
||||
|
||||
/// Return the root of a given [`StorageAddress`]: hash the pallet name and entry name
|
||||
/// and append those bytes to the output.
|
||||
pub fn write_storage_address_root_bytes<Address: StorageAddress>(
|
||||
addr: &Address,
|
||||
out: &mut Vec<u8>,
|
||||
) {
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.pallet_name().as_bytes()));
|
||||
out.extend(sp_crypto_hashing::twox_128(addr.entry_name().as_bytes()));
|
||||
}
|
||||
|
||||
/// Outputs the [`storage_address_root_bytes`] as well as any additional bytes that represent
|
||||
/// a lookup in a storage map at that location.
|
||||
pub fn storage_address_bytes<Address: StorageAddress>(
|
||||
addr: &Address,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut bytes = Vec::new();
|
||||
write_storage_address_root_bytes(addr, &mut bytes);
|
||||
addr.append_entry_bytes(metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Outputs a vector containing the bytes written by [`write_storage_address_root_bytes`].
|
||||
pub fn storage_address_root_bytes<Address: StorageAddress>(addr: &Address) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
write_storage_address_root_bytes(addr, &mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Take some SCALE encoded bytes and a [`StorageHasher`] and hash the bytes accordingly.
|
||||
pub fn hash_bytes(input: &[u8], hasher: StorageHasher, bytes: &mut Vec<u8>) {
|
||||
match hasher {
|
||||
StorageHasher::Identity => bytes.extend(input),
|
||||
StorageHasher::Blake2_128 => bytes.extend(sp_crypto_hashing::blake2_128(input)),
|
||||
StorageHasher::Blake2_128Concat => {
|
||||
bytes.extend(sp_crypto_hashing::blake2_128(input));
|
||||
bytes.extend(input);
|
||||
}
|
||||
StorageHasher::Blake2_256 => bytes.extend(sp_crypto_hashing::blake2_256(input)),
|
||||
StorageHasher::Twox128 => bytes.extend(sp_crypto_hashing::twox_128(input)),
|
||||
StorageHasher::Twox256 => bytes.extend(sp_crypto_hashing::twox_256(input)),
|
||||
StorageHasher::Twox64Concat => {
|
||||
bytes.extend(sp_crypto_hashing::twox_64(input));
|
||||
bytes.extend(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-12
@@ -14,21 +14,16 @@ use crate::macros::cfg_substrate_compat;
|
||||
mod tx_client;
|
||||
mod tx_progress;
|
||||
|
||||
pub use subxt_core::tx as tx_payload;
|
||||
pub use subxt_core::tx::signer;
|
||||
|
||||
// The PairSigner impl currently relies on Substrate bits and pieces, so make it an optional
|
||||
// feature if we want to avoid needing sp_core and sp_runtime.
|
||||
cfg_substrate_compat! {
|
||||
pub use signer::PairSigner;
|
||||
pub use subxt_core::tx::signer::PairSigner;
|
||||
}
|
||||
|
||||
pub use self::{
|
||||
signer::Signer,
|
||||
tx_client::{
|
||||
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
|
||||
ValidationResult,
|
||||
},
|
||||
tx_payload::{dynamic, DynamicPayload, Payload, TxPayload},
|
||||
tx_progress::{TxInBlock, TxProgress, TxStatus},
|
||||
pub use subxt_core::tx::payload::{dynamic, DynamicPayload, Payload, PayloadT};
|
||||
pub use subxt_core::tx::signer::{self, Signer};
|
||||
pub use tx_client::{
|
||||
PartialExtrinsic, SubmittableExtrinsic, TransactionInvalid, TransactionUnknown, TxClient,
|
||||
ValidationResult,
|
||||
};
|
||||
pub use tx_progress::{TxInBlock, TxProgress, TxStatus};
|
||||
|
||||
+56
-153
@@ -2,22 +2,16 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
backend::{BackendExt, BlockRef, TransactionStatus},
|
||||
client::{OfflineClientT, OnlineClientT},
|
||||
config::{
|
||||
Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher, Header, RefineParams,
|
||||
RefineParamsData,
|
||||
},
|
||||
error::{BlockError, Error, MetadataError},
|
||||
tx::{Signer as SignerT, TxPayload, TxProgress},
|
||||
utils::{Encoded, PhantomDataSendSync},
|
||||
config::{Config, ExtrinsicParams, Header, RefineParams, RefineParamsData},
|
||||
error::{BlockError, Error},
|
||||
tx::{PayloadT, Signer as SignerT, TxProgress},
|
||||
utils::PhantomDataSendSync,
|
||||
};
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use derive_where::derive_where;
|
||||
use sp_crypto_hashing::blake2_256;
|
||||
|
||||
/// A client for working with transactions.
|
||||
#[derive_where(Clone; Client)]
|
||||
@@ -43,65 +37,30 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
/// the pallet or call in question do not exist at all).
|
||||
pub fn validate<Call>(&self, call: &Call) -> Result<(), Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
{
|
||||
if let Some(details) = call.validation_details() {
|
||||
let expected_hash = self
|
||||
.client
|
||||
.metadata()
|
||||
.pallet_by_name_err(details.pallet_name)?
|
||||
.call_hash(details.call_name)
|
||||
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
|
||||
|
||||
if details.hash != expected_hash {
|
||||
return Err(MetadataError::IncompatibleCodegen.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
subxt_core::tx::validate(call, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Return the SCALE encoded bytes representing the call data of the transaction.
|
||||
pub fn call_data<Call>(&self, call: &Call) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
{
|
||||
let metadata = self.client.metadata();
|
||||
let mut bytes = Vec::new();
|
||||
call.encode_call_data_to(&metadata, &mut bytes)?;
|
||||
Ok(bytes)
|
||||
subxt_core::tx::call_data(call, &self.client.metadata()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Creates an unsigned extrinsic without submitting it.
|
||||
pub fn create_unsigned<Call>(&self, call: &Call) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
{
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
self.validate(call)?;
|
||||
|
||||
// 2. Encode extrinsic
|
||||
let extrinsic = {
|
||||
let mut encoded_inner = Vec::new();
|
||||
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
|
||||
4u8.encode_to(&mut encoded_inner);
|
||||
// encode call data after this byte.
|
||||
call.encode_call_data_to(&self.client.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(SubmittableExtrinsic::from_bytes(
|
||||
self.client.clone(),
|
||||
extrinsic,
|
||||
))
|
||||
subxt_core::tx::create_unsigned(call, &self.client.metadata())
|
||||
.map(|tx| SubmittableExtrinsic {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Create a partial extrinsic.
|
||||
@@ -114,25 +73,14 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
{
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
self.validate(call)?;
|
||||
|
||||
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
|
||||
let call_data = self.call_data(call)?;
|
||||
|
||||
// 3. Construct our custom additional/extra params.
|
||||
let additional_and_extra_params =
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(&self.client.client_state(), params)?;
|
||||
|
||||
// Return these details, ready to construct a signed extrinsic from.
|
||||
Ok(PartialExtrinsic {
|
||||
client: self.client.clone(),
|
||||
call_data,
|
||||
additional_and_extra_params,
|
||||
})
|
||||
subxt_core::tx::create_partial_signed(call, &self.client.client_state(), params)
|
||||
.map(|tx| PartialExtrinsic {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Creates a signed extrinsic without submitting it.
|
||||
@@ -146,19 +94,15 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
// with a hash allowing us to do so.
|
||||
self.validate(call)?;
|
||||
|
||||
// 2. Gather the "additional" and "extra" params along with the encoded call data,
|
||||
// ready to be signed.
|
||||
let partial_signed = self.create_partial_signed_offline(call, params)?;
|
||||
|
||||
// 3. Sign and construct an extrinsic from these details.
|
||||
Ok(partial_signed.sign(signer))
|
||||
subxt_core::tx::create_signed(call, &self.client.client_state(), signer, params)
|
||||
.map(|tx| SubmittableExtrinsic {
|
||||
client: self.client.clone(),
|
||||
inner: tx,
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +149,7 @@ where
|
||||
mut params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<PartialExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
{
|
||||
// Refine the params by adding account nonce and latest block information:
|
||||
self.refine_params(account_id, &mut params).await?;
|
||||
@@ -221,7 +165,7 @@ where
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<SubmittableExtrinsic<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
// 1. Validate this call against the current node metadata if the call comes
|
||||
@@ -249,7 +193,7 @@ where
|
||||
signer: &Signer,
|
||||
) -> Result<TxProgress<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
|
||||
{
|
||||
@@ -268,7 +212,7 @@ where
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<TxProgress<T, C>, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
self.create_signed(call, signer, params)
|
||||
@@ -293,7 +237,7 @@ where
|
||||
signer: &Signer,
|
||||
) -> Result<T::Hash, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
<T::ExtrinsicParams as ExtrinsicParams<T>>::Params: Default,
|
||||
{
|
||||
@@ -315,7 +259,7 @@ where
|
||||
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
|
||||
) -> Result<T::Hash, Error>
|
||||
where
|
||||
Call: TxPayload,
|
||||
Call: PayloadT,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
self.create_signed(call, signer, params)
|
||||
@@ -328,8 +272,7 @@ where
|
||||
/// This payload contains the information needed to produce an extrinsic.
|
||||
pub struct PartialExtrinsic<T: Config, C> {
|
||||
client: C,
|
||||
call_data: Vec<u8>,
|
||||
additional_and_extra_params: T::ExtrinsicParams,
|
||||
inner: subxt_core::tx::PartialTransaction<T>,
|
||||
}
|
||||
|
||||
impl<T, C> PartialExtrinsic<T, C>
|
||||
@@ -337,34 +280,16 @@ where
|
||||
T: Config,
|
||||
C: OfflineClientT<T>,
|
||||
{
|
||||
// Obtain bytes representing the signer payload and run call some function
|
||||
// with them. This can avoid an allocation in some cases when compared to
|
||||
// [`PartialExtrinsic::signer_payload()`].
|
||||
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_extra_to(&mut bytes);
|
||||
self.additional_and_extra_params
|
||||
.encode_additional_to(&mut bytes);
|
||||
if bytes.len() > 256 {
|
||||
f(Cow::Borrowed(blake2_256(&bytes).as_ref()))
|
||||
} else {
|
||||
f(Cow::Owned(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the 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())
|
||||
self.inner.signer_payload()
|
||||
}
|
||||
|
||||
/// Return the bytes representing the call data for this partially constructed
|
||||
/// extrinsic.
|
||||
pub fn call_data(&self) -> &[u8] {
|
||||
&self.call_data
|
||||
self.inner.call_data()
|
||||
}
|
||||
|
||||
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
|
||||
@@ -374,10 +299,10 @@ where
|
||||
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_address_and_signature(&signer.address(), &signature)
|
||||
SubmittableExtrinsic {
|
||||
client: self.client.clone(),
|
||||
inner: self.inner.sign(signer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this [`PartialExtrinsic`] into a [`SubmittableExtrinsic`], ready to submit.
|
||||
@@ -389,40 +314,19 @@ where
|
||||
address: &T::Address,
|
||||
signature: &T::Signature,
|
||||
) -> SubmittableExtrinsic<T, C> {
|
||||
// Encode the extrinsic (into the format expected by protocol version 4)
|
||||
let extrinsic = {
|
||||
let mut encoded_inner = Vec::new();
|
||||
// "is signed" + transaction protocol version (4)
|
||||
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
|
||||
// from address for signature
|
||||
address.encode_to(&mut encoded_inner);
|
||||
// the signature
|
||||
signature.encode_to(&mut encoded_inner);
|
||||
// attach custom extra params
|
||||
self.additional_and_extra_params
|
||||
.encode_extra_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.
|
||||
SubmittableExtrinsic::from_bytes(self.client.clone(), extrinsic)
|
||||
SubmittableExtrinsic {
|
||||
client: self.client.clone(),
|
||||
inner: self
|
||||
.inner
|
||||
.sign_with_address_and_signature(address, signature),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents an extrinsic that has been signed and is ready to submit.
|
||||
pub struct SubmittableExtrinsic<T, C> {
|
||||
client: C,
|
||||
encoded: Encoded,
|
||||
marker: std::marker::PhantomData<T>,
|
||||
inner: subxt_core::tx::Transaction<T>,
|
||||
}
|
||||
|
||||
impl<T, C> SubmittableExtrinsic<T, C>
|
||||
@@ -440,25 +344,24 @@ where
|
||||
pub fn from_bytes(client: C, tx_bytes: Vec<u8>) -> Self {
|
||||
Self {
|
||||
client,
|
||||
encoded: Encoded(tx_bytes),
|
||||
marker: std::marker::PhantomData,
|
||||
inner: subxt_core::tx::Transaction::from_bytes(tx_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate and return the hash of the extrinsic, based on the configured hasher.
|
||||
pub fn hash(&self) -> T::Hash {
|
||||
T::Hasher::hash_of(&self.encoded)
|
||||
self.inner.hash()
|
||||
}
|
||||
|
||||
/// Returns the SCALE encoded extrinsic bytes.
|
||||
pub fn encoded(&self) -> &[u8] {
|
||||
&self.encoded.0
|
||||
self.inner.encoded()
|
||||
}
|
||||
|
||||
/// Consumes [`SubmittableExtrinsic`] and returns the SCALE encoded
|
||||
/// extrinsic bytes.
|
||||
pub fn into_encoded(self) -> Vec<u8> {
|
||||
self.encoded.0
|
||||
self.inner.into_encoded()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,7 +382,7 @@ where
|
||||
let sub = self
|
||||
.client
|
||||
.backend()
|
||||
.submit_transaction(&self.encoded.0)
|
||||
.submit_transaction(self.encoded())
|
||||
.await?;
|
||||
|
||||
Ok(TxProgress::new(sub, self.client.clone(), ext_hash))
|
||||
@@ -495,7 +398,7 @@ where
|
||||
let mut sub = self
|
||||
.client
|
||||
.backend()
|
||||
.submit_transaction(&self.encoded.0)
|
||||
.submit_transaction(self.encoded())
|
||||
.await?;
|
||||
|
||||
// If we get a bad status or error back straight away then error, else return the hash.
|
||||
@@ -543,7 +446,7 @@ where
|
||||
let block_hash = at.into().hash();
|
||||
|
||||
// Approach taken from https://github.com/paritytech/json-rpc-interface-spec/issues/55.
|
||||
let mut params = Vec::with_capacity(8 + self.encoded.0.len() + 8);
|
||||
let mut params = Vec::with_capacity(8 + self.encoded().len() + 8);
|
||||
2u8.encode_to(&mut params);
|
||||
params.extend(self.encoded().iter());
|
||||
block_hash.encode_to(&mut params);
|
||||
|
||||
@@ -52,8 +52,8 @@ async fn chainhead_unstable_follow() {
|
||||
FollowEvent::Initialized(init) => {
|
||||
assert_eq!(init.finalized_block_hashes, vec![finalized_block_hash]);
|
||||
if let Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec })) = init.finalized_block_runtime {
|
||||
assert_eq!(spec.spec_version, runtime_version.spec_version());
|
||||
assert_eq!(spec.transaction_version, runtime_version.transaction_version());
|
||||
assert_eq!(spec.spec_version, runtime_version.spec_version);
|
||||
assert_eq!(spec.transaction_version, runtime_version.transaction_version);
|
||||
} else {
|
||||
panic!("runtime details not provided with init event, got {:?}", init.finalized_block_runtime);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ async fn chainhead_unstable_body() {
|
||||
let mut blocks = rpc.chainhead_unstable_follow(false).await.unwrap();
|
||||
let event = blocks.next().await.unwrap().unwrap();
|
||||
let hash = match event {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
|
||||
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap();
|
||||
@@ -98,7 +98,7 @@ async fn chainhead_unstable_header() {
|
||||
let mut blocks = rpc.chainhead_unstable_follow(false).await.unwrap();
|
||||
let event = blocks.next().await.unwrap().unwrap();
|
||||
let hash = match event {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
|
||||
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap();
|
||||
@@ -126,7 +126,7 @@ async fn chainhead_unstable_storage() {
|
||||
let mut blocks = rpc.chainhead_unstable_follow(false).await.unwrap();
|
||||
let event = blocks.next().await.unwrap().unwrap();
|
||||
let hash = match event {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
|
||||
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap();
|
||||
@@ -171,7 +171,7 @@ async fn chainhead_unstable_call() {
|
||||
let mut blocks = rpc.chainhead_unstable_follow(true).await.unwrap();
|
||||
let event = blocks.next().await.unwrap().unwrap();
|
||||
let hash = match event {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
|
||||
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap();
|
||||
@@ -208,7 +208,7 @@ async fn chainhead_unstable_unpin() {
|
||||
let mut blocks = rpc.chainhead_unstable_follow(true).await.unwrap();
|
||||
let event = blocks.next().await.unwrap().unwrap();
|
||||
let hash = match event {
|
||||
FollowEvent::Initialized(init) => init.finalized_block_hashes.last().unwrap().clone(),
|
||||
FollowEvent::Initialized(init) => *init.finalized_block_hashes.last().unwrap(),
|
||||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap();
|
||||
@@ -414,7 +414,7 @@ async fn transaction_unstable_stop() {
|
||||
.unwrap()
|
||||
.expect("Server is not overloaded by 1 tx; qed");
|
||||
|
||||
let _ = rpc.transaction_unstable_stop(&operation_id).await.unwrap();
|
||||
rpc.transaction_unstable_stop(&operation_id).await.unwrap();
|
||||
// Cannot stop it twice.
|
||||
let _err = rpc
|
||||
.transaction_unstable_stop(&operation_id)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,8 +35,11 @@ fn construct_offline_client() -> OfflineClient<PolkadotConfig> {
|
||||
let bytes = hex::decode(h).unwrap();
|
||||
H256::from_slice(&bytes)
|
||||
};
|
||||
let runtime_version = subxt::client::RuntimeVersion::new(9370, 20);
|
||||
|
||||
let runtime_version = subxt::client::RuntimeVersion {
|
||||
spec_version: 9370,
|
||||
transaction_version: 20,
|
||||
};
|
||||
|
||||
let metadata = {
|
||||
let bytes = std::fs::read("../../../../artifacts/metadata_with_custom_values.scale").unwrap();
|
||||
Metadata::decode(&mut &*bytes).unwrap()
|
||||
|
||||
Reference in New Issue
Block a user