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:
Alexandru Vasile
2023-05-10 13:18:39 +03:00
committed by GitHub
parent b632ce7673
commit 3f16bb8d52
19 changed files with 6493 additions and 164 deletions
+132 -1
View File
@@ -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![],
},
+2 -2
View File
@@ -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};