Skip to content

Commit

Permalink
extrinsics: Decode extrinsics from blocks (#929)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
2 people authored and tadeohepperle committed May 10, 2023
1 parent 0f963f2 commit 3b0022b
Show file tree
Hide file tree
Showing 19 changed files with 3,281 additions and 163 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions codegen/src/api/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ pub fn generate_calls(
// The call structure's documentation was stripped above.
let call_struct = quote! {
#struct_def

impl #crate_path::blocks::StaticExtrinsic for #struct_name {
const PALLET: &'static str = #pallet_name;
const CALL: &'static str = #call_name;
}
};

let client_fn = quote! {
Expand All @@ -106,6 +111,7 @@ pub fn generate_calls(
.into_iter()
.unzip();

let call_type = type_gen.resolve_type_path(call.ty.id);
let call_ty = type_gen.resolve_type(call.ty.id);
let docs = &call_ty.docs;
let docs = should_gen_docs
Expand All @@ -114,6 +120,7 @@ pub fn generate_calls(

Ok(quote! {
#docs
pub type Call = #call_type;
pub mod calls {
use super::root_mod;
use super::#types_mod_ident;
Expand Down
49 changes: 49 additions & 0 deletions codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,26 @@ impl RuntimeGenerator {
}
};

let outer_extrinsic_variants = self.metadata.pallets.iter().filter_map(|p| {
let variant_name = format_ident!("{}", p.name);
let mod_name = format_ident!("{}", p.name.to_string().to_snake_case());
let index = proc_macro2::Literal::u8_unsuffixed(p.index);

p.calls.as_ref().map(|_| {
quote! {
#[codec(index = #index)]
#variant_name(#mod_name::Call),
}
})
});

let outer_extrinsic = quote! {
#default_derives
pub enum Call {
#( #outer_extrinsic_variants )*
}
};

let root_event_if_arms = self.metadata.pallets.iter().filter_map(|p| {
let variant_name_str = &p.name;
let variant_name = format_ident!("{}", variant_name_str);
Expand All @@ -385,6 +405,24 @@ impl RuntimeGenerator {
})
});

let root_extrinsic_if_arms = self.metadata.pallets.iter().filter_map(|p| {
let variant_name_str = &p.name;
let variant_name = format_ident!("{}", variant_name_str);
let mod_name = format_ident!("{}", variant_name_str.to_string().to_snake_case());
p.calls.as_ref().map(|_| {
// An 'if' arm for the RootExtrinsic impl to match this variant name:
quote! {
if pallet_name == #variant_name_str {
return Ok(Call::#variant_name(#mod_name::Call::decode_with_metadata(
&mut &*pallet_bytes,
pallet_ty,
metadata
)?));
}
}
})
});

let outer_error_variants = self.metadata.pallets.iter().filter_map(|p| {
let variant_name = format_ident!("{}", p.name);
let mod_name = format_ident!("{}", p.name.to_string().to_snake_case());
Expand Down Expand Up @@ -483,7 +521,18 @@ impl RuntimeGenerator {
}
}

#outer_extrinsic

impl #crate_path::blocks::RootExtrinsic for Call {
fn root_extrinsic(pallet_bytes: &[u8], pallet_name: &str, pallet_ty: u32, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
#( #root_extrinsic_if_arms )*
Err(#crate_path::ext::scale_decode::Error::custom(format!("Pallet name '{}' not found in root Call enum", pallet_name)).into())
}
}

#outer_error

impl #crate_path::error::RootError for Error {
fn root_error(pallet_bytes: &[u8], pallet_name: &str, metadata: &#crate_path::Metadata) -> Result<Self, #crate_path::Error> {
use #crate_path::metadata::DecodeWithMetadata;
Expand Down
5 changes: 5 additions & 0 deletions codegen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ pub enum CodegenError {
"{0} type should be an variant/enum type. Make sure you are providing a valid substrate-based metadata"
)]
InvalidType(String),
/// Extrinsic call type could not be found.
#[error(
"Extrinsic call type could not be found. Make sure you are providing a valid substrate-based metadata"
)]
MissingCallType,
}

impl CodegenError {
Expand Down
99 changes: 99 additions & 0 deletions examples/examples/block_extrinsics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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.

//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```

use futures::StreamExt;
use sp_keyring::AccountKeyring;
use std::time::Duration;
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
pub mod polkadot {}

/// Subscribe to all events, and then manually look through them and
/// pluck out the events that we care about.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;

// Subscribe to (in this case, finalized) blocks.
let mut block_sub = api.blocks().subscribe_finalized().await?;

// While this subscription is active, balance transfers are made somewhere:
tokio::task::spawn({
let api = api.clone();
async move {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let mut transfer_amount = 1_000_000_000;

// Make small balance transfers from Alice to Bob in a loop:
loop {
let transfer_tx = polkadot::tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount);
api.tx()
.sign_and_submit_default(&transfer_tx, &signer)
.await
.unwrap();

tokio::time::sleep(Duration::from_secs(10)).await;
transfer_amount += 100_000_000;
}
}
});

// Get each finalized block as it arrives.
while let Some(block) = block_sub.next().await {
let block = block?;

let block_hash = block.hash();
println!(" Block {:?}", block_hash);

// Ask for the extrinsics for this block.
let extrinsics = block.body().await?.extrinsics();

let transfer_tx = extrinsics.find_first::<polkadot::balances::calls::types::Transfer>();
println!(" Transfer tx: {:?}", transfer_tx);

// Ask for the extrinsics for this block.
for extrinsic in extrinsics.iter() {
let extrinsic = extrinsic?;

println!(
" Extrinsic block index {:?}, pallet index {:?}, variant index {:?}",
extrinsic.index(),
extrinsic.pallet_index(),
extrinsic.variant_index()
);

let root_extrinsic = extrinsic.as_root_extrinsic::<polkadot::Call>();
println!(" As root extrinsic {:?}\n", root_extrinsic);

let pallet_extrinsic = extrinsic
.as_pallet_extrinsic::<polkadot::runtime_types::pallet_balances::pallet::Call>();
println!(
" Extrinsic as Balances Pallet call: {:?}\n",
pallet_extrinsic
);

let call = extrinsic.as_extrinsic::<polkadot::balances::calls::types::Transfer>()?;
println!(
" Extrinsic as polkadot::balances::calls::Transfer: {:?}\n\n",
call
);
}

println!("\n");
}

Ok(())
}
3 changes: 2 additions & 1 deletion examples/examples/blocks_subscribing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

// Log each of the extrinsic with it's associated events:
let body = block.body().await?;
for ext in body.extrinsics() {
for ext in body.extrinsics().iter() {
let ext = ext?;
let idx = ext.index();
let events = ext.events().await?;
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));
Expand Down
1 change: 1 addition & 0 deletions subxt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-keyring = { workspace = true }
sp-version = { workspace = true }
assert_matches = { workspace = true }
Loading

0 comments on commit 3b0022b

Please sign in to comment.