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:
James Wilson
2024-04-15 15:20:11 +01:00
committed by GitHub
parent b527c857ea
commit 1e111ea9db
89 changed files with 4459 additions and 3500 deletions
Generated
+258 -237
View File
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.
+5 -2
View File
@@ -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)
}
+2 -2
View File
@@ -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, )* },
+2 -2
View File
@@ -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,)*]
+2 -2
View File
@@ -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,)*])
}
))
}
+2 -2
View File
@@ -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, )* },
+9
View File
@@ -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;
}
+4
View File
@@ -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)
}
}
+709
View File
@@ -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
View File
@@ -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)
}
+23
View File
@@ -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
}
}
+16 -48
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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.
+7 -9
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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;
+45 -23
View File
@@ -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
View File
@@ -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
View File
@@ -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)]
+62 -16
View File
@@ -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
View File
@@ -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;
+4
View File
@@ -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 -1
View File
@@ -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.
+15 -71
View File
@@ -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)
}
}
+26 -6
View File
@@ -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
View File
@@ -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)
}
+188
View File
@@ -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
View File
@@ -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)
}
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
+178
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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.
+3 -6
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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.
+334 -221
View File
File diff suppressed because it is too large Load Diff
+48 -33
View File
@@ -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")
};
+2 -2
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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,
};
+4 -1
View File
@@ -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):
+1 -1
View File
@@ -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,
})
+3 -2
View File
@@ -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(())
}
+10 -6
View File
@@ -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)))
}
+4 -1
View File
@@ -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)))
});
+2 -4
View File
@@ -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.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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.
//!
+2 -2
View File
@@ -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()`].
//!
+1 -1
View File
@@ -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.
//!
+16 -19
View File
@@ -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),
+9 -14
View File
@@ -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;
}
};
+5 -8
View File
@@ -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)
}
}
+1 -1
View File
@@ -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};
+12 -15
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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()))
}
}
}
+1 -1
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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};
+18 -30
View File
@@ -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)
}
}
+3 -15
View File
@@ -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,
};
+9 -17
View File
@@ -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)
}
}
+18 -94
View File
@@ -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)
}
-60
View File
@@ -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
View File
@@ -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
View File
@@ -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()