mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 22:11:06 +00:00
extrinsics: Decode extrinsics from blocks (#929)
* Update polkadot.scale Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Add extrinsics client Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Decode extrinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add extrinsic error Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Expose extrinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Fetch and decode block extrinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Fetch pallet and variant index Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Move extrinsics on the subxt::blocks Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * example: Adjust example Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata: Collect ExtrinsicMetadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Implement StaticExtrinsic for the calls Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust examples Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * codegen: Add root level Call enum Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust testing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Add new decode interface Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * subxt: Merge ExtrinsicError with BlockError Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Find first extrinsic Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Move code to extrinsic_types Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Add Extrinsic struct Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust examples Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * test: Decode extinsics Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Add fake metadata for static decoding Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Decode from insufficient bytes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Check unsupported versions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Statically decode to root and pallet enums Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/tests: Remove clones Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Fetch block body inline Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Rename ExtrinsicIds to ExtrinsicPartTypeIds Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics/test: Check decode as_extrinsic Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Remove InsufficientData error Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * blocks: Return error from extrinsic_metadata Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * extrinsics: Postpone decoding of call bytes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * metadata_type: Rename variables Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Adjust calls path for example and tests Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * examples: Remove traces Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * book: Add extrinsics documentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * book: Improve extrinsics docs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: James Wilson <james@jsdw.me>
This commit is contained in:
@@ -27,6 +27,9 @@ pub enum MetadataError {
|
||||
/// Event is not in metadata.
|
||||
#[error("Pallet {0}, Event {0} not found")]
|
||||
EventNotFound(u8, u8),
|
||||
/// Extrinsic is not in metadata.
|
||||
#[error("Pallet {0}, Extrinsic {0} not found")]
|
||||
ExtrinsicNotFound(u8, u8),
|
||||
/// Event is not in metadata.
|
||||
#[error("Pallet {0}, Error {0} not found")]
|
||||
ErrorNotFound(u8, u8),
|
||||
@@ -75,6 +78,8 @@ struct MetadataInner {
|
||||
|
||||
// Events are hashed by pallet an error index (decode oriented)
|
||||
events: HashMap<(u8, u8), EventMetadata>,
|
||||
// Extrinsics are hashed by pallet an error index (decode oriented)
|
||||
extrinsics: HashMap<(u8, u8), ExtrinsicMetadata>,
|
||||
// Errors are hashed by pallet and error index (decode oriented)
|
||||
errors: HashMap<(u8, u8), ErrorMetadata>,
|
||||
|
||||
@@ -135,6 +140,20 @@ impl Metadata {
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
/// Returns the metadata for the extrinsic at the given pallet and call indices.
|
||||
pub fn extrinsic(
|
||||
&self,
|
||||
pallet_index: u8,
|
||||
call_index: u8,
|
||||
) -> Result<&ExtrinsicMetadata, MetadataError> {
|
||||
let event = self
|
||||
.inner
|
||||
.extrinsics
|
||||
.get(&(pallet_index, call_index))
|
||||
.ok_or(MetadataError::ExtrinsicNotFound(pallet_index, call_index))?;
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
/// Returns the metadata for the error at the given pallet and error indices.
|
||||
pub fn error(
|
||||
&self,
|
||||
@@ -386,6 +405,39 @@ impl EventMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for specific extrinsics.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExtrinsicMetadata {
|
||||
// The pallet name is shared across every extrinsic, so put it
|
||||
// behind an Arc to avoid lots of needless clones of it existing.
|
||||
pallet: Arc<str>,
|
||||
call: String,
|
||||
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
|
||||
docs: Vec<String>,
|
||||
}
|
||||
|
||||
impl ExtrinsicMetadata {
|
||||
/// Get the name of the pallet from which the extrinsic was emitted.
|
||||
pub fn pallet(&self) -> &str {
|
||||
&self.pallet
|
||||
}
|
||||
|
||||
/// Get the name of the extrinsic call.
|
||||
pub fn call(&self) -> &str {
|
||||
&self.call
|
||||
}
|
||||
|
||||
/// The names, type names & types of each field in the extrinsic.
|
||||
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
/// Documentation for this extrinsic.
|
||||
pub fn docs(&self) -> &[String] {
|
||||
&self.docs
|
||||
}
|
||||
}
|
||||
|
||||
/// Details about a specific runtime error.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ErrorMetadata {
|
||||
@@ -426,6 +478,12 @@ pub enum InvalidMetadataError {
|
||||
/// Type missing from type registry
|
||||
#[error("Type {0} missing from type registry")]
|
||||
MissingType(u32),
|
||||
/// Type missing extrinsic "Call" type
|
||||
#[error("Missing extrinsic Call type")]
|
||||
MissingCallType,
|
||||
/// The extrinsic variant expected to contain a single field.
|
||||
#[error("Extrinsic variant at index {0} expected to contain a single field")]
|
||||
InvalidExtrinsicVariant(u8),
|
||||
/// Type was not a variant/enum type
|
||||
#[error("Type {0} was not a variant/enum type")]
|
||||
TypeDefNotVariant(u32),
|
||||
@@ -596,11 +654,60 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
|
||||
.find(|ty| ty.ty.path.segments == ["sp_runtime", "DispatchError"])
|
||||
.map(|ty| ty.id);
|
||||
|
||||
let extrinsic_ty = metadata
|
||||
.types
|
||||
.resolve(metadata.extrinsic.ty.id)
|
||||
.ok_or(InvalidMetadataError::MissingType(metadata.extrinsic.ty.id))?;
|
||||
|
||||
let Some(call_id) = extrinsic_ty.type_params
|
||||
.iter()
|
||||
.find(|ty| ty.name == "Call")
|
||||
.and_then(|ty| ty.ty)
|
||||
.map(|ty| ty.id) else {
|
||||
return Err(InvalidMetadataError::MissingCallType);
|
||||
};
|
||||
|
||||
let call_type_variants = get_type_def_variant(call_id)?;
|
||||
|
||||
let mut extrinsics = HashMap::<(u8, u8), ExtrinsicMetadata>::new();
|
||||
for variant in &call_type_variants.variants {
|
||||
let pallet_name: Arc<str> = variant.name.to_string().into();
|
||||
let pallet_index = variant.index;
|
||||
|
||||
// Pallet variants must contain one single call variant.
|
||||
// In the following form:
|
||||
//
|
||||
// enum RuntimeCall {
|
||||
// Pallet(pallet_call)
|
||||
// }
|
||||
if variant.fields.len() != 1 {
|
||||
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
|
||||
}
|
||||
let Some(ty) = variant.fields.first() else {
|
||||
return Err(InvalidMetadataError::InvalidExtrinsicVariant(pallet_index));
|
||||
};
|
||||
|
||||
// Get the call variant.
|
||||
let call_type_variant = get_type_def_variant(ty.ty.id)?;
|
||||
for variant in &call_type_variant.variants {
|
||||
extrinsics.insert(
|
||||
(pallet_index, variant.index),
|
||||
ExtrinsicMetadata {
|
||||
pallet: pallet_name.clone(),
|
||||
call: variant.name.to_string(),
|
||||
fields: variant.fields.clone(),
|
||||
docs: variant.docs.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Metadata {
|
||||
inner: Arc::new(MetadataInner {
|
||||
metadata,
|
||||
pallets,
|
||||
events,
|
||||
extrinsics,
|
||||
errors,
|
||||
dispatch_error_ty,
|
||||
runtime_apis,
|
||||
@@ -624,6 +731,30 @@ mod tests {
|
||||
use scale_info::{meta_type, TypeInfo};
|
||||
|
||||
fn load_metadata() -> Metadata {
|
||||
// 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.
|
||||
// In reality, the extrinsic type has "Call", "Address", "Extra", "Signature" generic types.
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct ExtrinsicType<Call> {
|
||||
call: 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(TypeInfo)]
|
||||
enum RuntimeCall {
|
||||
PalletName(Pallet),
|
||||
}
|
||||
// The calls of the pallet.
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
enum Pallet {
|
||||
#[allow(unused)]
|
||||
SomeCall,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(TypeInfo)]
|
||||
@@ -662,7 +793,7 @@ mod tests {
|
||||
let metadata = RuntimeMetadataV15::new(
|
||||
vec![pallet],
|
||||
ExtrinsicMetadata {
|
||||
ty: meta_type::<()>(),
|
||||
ty: meta_type::<ExtrinsicType<RuntimeCall>>(),
|
||||
version: 0,
|
||||
signed_extensions: vec![],
|
||||
},
|
||||
|
||||
@@ -12,8 +12,8 @@ mod metadata_type;
|
||||
pub use metadata_location::MetadataLocation;
|
||||
|
||||
pub use metadata_type::{
|
||||
ErrorMetadata, EventMetadata, InvalidMetadataError, Metadata, MetadataError, PalletMetadata,
|
||||
RuntimeFnMetadata,
|
||||
ErrorMetadata, EventMetadata, ExtrinsicMetadata, InvalidMetadataError, Metadata, MetadataError,
|
||||
PalletMetadata, RuntimeFnMetadata,
|
||||
};
|
||||
|
||||
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
|
||||
|
||||
Reference in New Issue
Block a user