Skip to content

Commit

Permalink
Improve Signed Extension and Block Decoding Examples/Book (#1357)
Browse files Browse the repository at this point in the history
* asset hub example and book adjustment

* formatting

* recursive derives

* polkadot monitor example and book adjustments

* formatting

* adjust docs and examples, add dynamic example

* james suggestions

* fmt

* chore(subxt/src): typo fix (#1370)

* rpcmethods

* followstr

* mod and else

* Weekly Cronjob fetching artifacts and generating polkadot.rs file. (#1352)

* github CI action cronjob

* add commit message

* fix the CI yml files

* binary crate for CI script with substrate-runner

* update the CI script

* correct the artifacts script

* remove bash script

---------

Co-authored-by: James Wilson <[email protected]>
Co-authored-by: Pan chao <[email protected]>
  • Loading branch information
3 people committed Jan 18, 2024
1 parent 833beb2 commit f29fbed
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 15 deletions.
46 changes: 46 additions & 0 deletions subxt/examples/block_decoding_dynamic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client that subscribes to blocks of the Polkadot network.
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;

// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");

// Decode each signed extrinsic in the block dynamically
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let ext = ext?;

let Some(signed_extensions) = ext.signed_extensions() else {
continue; // we do not look at inherents in this example
};

let meta = ext.extrinsic_metadata()?;
let fields = ext.field_values()?;

println!(" {}/{}", meta.pallet.name(), meta.variant.name);
println!(" Signed Extensions:");
for signed_ext in signed_extensions.iter() {
let signed_ext = signed_ext?;
// We only want to take a look at these 3 signed extensions, because the others all just have unit fields.
if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"]
.contains(&signed_ext.name())
{
println!(" {}: {}", signed_ext.name(), signed_ext.value()?);
}
}
println!(" Fields:");
println!(" {}\n", fields);
}
}

Ok(())
}
65 changes: 65 additions & 0 deletions subxt/examples/block_decoding_static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#![allow(missing_docs)]
use subxt::{
utils::{AccountId32, MultiAddress},
OnlineClient, PolkadotConfig,
};

use codec::Decode;

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

use polkadot::balances::calls::types::TransferKeepAlive;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client that subscribes to blocks of the Polkadot network.
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;

// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;

// For each block, print details about the `TransferKeepAlive` transactions we are interested in.
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash}):");

let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let ext = ext?;
if let Ok(Some(transfer)) = ext.as_extrinsic::<TransferKeepAlive>() {
let Some(extensions) = ext.signed_extensions() else {
panic!("TransferKeepAlive should be signed")
};

ext.address_bytes().unwrap();
let addr_bytes = ext
.address_bytes()
.expect("TransferKeepAlive should be signed");
let sender = MultiAddress::<AccountId32, ()>::decode(&mut &addr_bytes[..])
.expect("Decoding should work");
let sender = display_address(&sender);
let receiver = display_address(&transfer.dest);
let value = transfer.value;
let tip = extensions.tip().expect("Should have tip");
let nonce = extensions.nonce().expect("Should have nonce");

println!(
" Transfer of {value} DOT:\n {sender} (Tip: {tip}, Nonce: {nonce}) ---> {receiver}",
);
}
}
}

Ok(())
}

fn display_address(addr: &MultiAddress<AccountId32, ()>) -> String {
if let MultiAddress::Id(id32) = addr {
format!("{id32}")
} else {
"MultiAddress::...".into()
}
}
2 changes: 1 addition & 1 deletion subxt/examples/blocks_subscribing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(" Extrinsic #{idx}:");
println!(" Bytes: {bytes_hex}");
println!(" Decoded: {decoded_ext:?}");
println!(" Events:");

println!(" Events:");
for evt in events.iter() {
let evt = evt?;
let pallet_name = evt.pallet_name();
Expand Down
54 changes: 54 additions & 0 deletions subxt/examples/setup_config_assethub.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#![allow(missing_docs)]
use subxt::config::{
Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, PolkadotConfig, SubstrateConfig,
};
use subxt_signer::sr25519::dev;

#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
derive_for_type(
path = "xcm::v2::multilocation::MultiLocation",
derive = "Clone",
recursive
)
)]
pub mod runtime {}
use runtime::runtime_types::xcm::v2::multilocation::{Junctions, MultiLocation};

// We don't need to construct this at runtime, so an empty enum is appropriate.
pub enum AssetHubConfig {}

impl Config for AssetHubConfig {
type Hash = <SubstrateConfig as Config>::Hash;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <PolkadotConfig as Config>::Address;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type ExtrinsicParams = DefaultExtrinsicParams<AssetHubConfig>;
// Here we use the MultiLocation from the metadata as a part of the config:
// The `ChargeAssetTxPayment` signed extension that is part of the ExtrinsicParams above, now uses the type:
type AssetId = MultiLocation;
}

#[tokio::main]
async fn main() {
// With the config defined, we can create an extrinsic with subxt:
let client = subxt::OnlineClient::<AssetHubConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());

// Build extrinsic params using an asset at this location as a tip:
let location: MultiLocation = MultiLocation {
parents: 3,
interior: Junctions::Here,
};
let tx_config = DefaultExtrinsicParamsBuilder::<AssetHubConfig>::new()
.tip_of(1234, location)
.build();

// And provide the extrinsic params including the tip when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config)
.await;
}
12 changes: 2 additions & 10 deletions subxt/examples/setup_config_custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,8 @@ use subxt::client::OfflineClientT;
use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
use subxt_signer::sr25519::dev;

#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
derive_for_type(
path = "xcm::v2::multilocation::MultiLocation",
derive = "Clone",
recursive
)
)]
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
pub mod runtime {}
use runtime::runtime_types::xcm::v2::multilocation::MultiLocation;

// We don't need to construct this at runtime,
// so an empty enum is appropriate:
Expand All @@ -27,7 +19,7 @@ impl Config for CustomConfig {
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = CustomExtrinsicParams<Self>;
type AssetId = MultiLocation;
type AssetId = u32;
}

// This represents some arbitrary (and nonsensical) custom parameters that
Expand Down
12 changes: 12 additions & 0 deletions subxt/src/book/setup/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,15 @@
//! ```rust,ignore
#![doc = include_str ! ("../../../examples/setup_config_custom.rs")]
//! ```
//!
//! ### Using a type from the metadata as a config parameter
//!
//! You can also use types that are generated from chain metadata as type parameters of the Config trait.
//! Just make sure all trait bounds are satisfied. This can often be achieved by using custom derives with the subxt macro.
//! For example, the AssetHub Parachain expects tips to include a `MultiLocation`, which is a type we can draw from the metadata.
//!
//! This example shows what using the `MultiLocation` struct as part of your config would look like in subxt:
//!
//! ```rust,ignore
#![doc = include_str ! ("../../../examples/setup_config_assethub.rs")]
//! ```
67 changes: 63 additions & 4 deletions subxt/src/book/usage/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,72 @@
//! Aside from these links to other Subxt APIs, the main thing that we can do here is iterate over and
//! decode the extrinsics in a block body.
//!
//! ## Example
//! ## Decoding Extrinsics
//!
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and iterate over
//! the extrinsics stored within it. From there, you can decode the extrinsics and access various details,
//! including the associated events:
//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and [iterate over
//! the extrinsics](crate::blocks::Extrinsics::iter()) stored within it. The extrinsics yielded are of type
//! [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), which is just a blob of bytes that also stores which
//! pallet and call in that pallet it belongs to. It also contains information about signed extensions that
//! have been used for submitting this extrinsic.
//!
//! To use the extrinsic, you probably want to decode it into a concrete Rust type. These Rust types representing
//! extrinsics from different pallets can be generated from metadata using the subxt macro or the CLI tool.
//!
//! When decoding the extrinsic into a static type you have two options:
//!
//! ### Statically decode the extrinsics into [the root extrinsic type](crate::blocks::ExtrinsicDetails::as_root_extrinsic())
//!
//! The root extrinsic type generated by subxt is a Rust enum with one variant for each pallet. Each of these
//! variants has a field that is another enum whose variants cover all calls of the respective pallet.
//! If the extrinsic bytes are valid and your metadata matches the chain's metadata, decoding the bytes of an extrinsic into
//! this root extrinsic type should always succeed.
//!
//! This example shows how to subscribe to blocks and decode the extrinsics in each block into the root extrinsic type.
//! Once we get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), we can decode it statically or dynamically.
//! We can also access details about the extrinsic, including the associated events and signed extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/blocks_subscribing.rs")]
//! ```
//!
//! ### Statically decode the extrinsic into [a specific pallet call](crate::blocks::ExtrinsicDetails::as_extrinsic())
//!
//! This is useful if you are expecting a specific extrinsic to be part of some block. If the extrinsic you try to decode
//! is a different extrinsic, an `Ok(None)` value is returned from [`as_extrinsic::<T>()`](crate::blocks::ExtrinsicDetails::as_extrinsic());
//!
//! If you are only interested in finding specific extrinsics in a block, you can also [iterate over all of them](crate::blocks::Extrinsics::find),
//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last).
//!
//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network.
//! We statically decode them and access the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicSignedExtensions::nonce()) signed extensions.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/block_decoding_static.rs")]
//! ```
//!
//! ### Dynamically decode the extrinsic
//!
//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated
//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()),
//! which gives you access to it's fields as a [scale value composite](scale_value::Composite).
//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and signed extensions dynamically.
//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig).
//! Other than that it works in a chain-agnostic way:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/block_decoding_dynamic.rs")]
//! ```
//!
//! ## Decoding signed extensions
//!
//! Extrinsics can contain signed extensions. The signed extensions can be different across chains.
//! The [Config](crate::Config) implementation for your chain defines which signed extensions you expect.
//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in,
//! you can try to [get its signed extensions](crate::blocks::ExtrinsicDetails::signed_extensions()).
//! These are only available on signed extrinsics. You can try to [find a specific signed extension](crate::blocks::ExtrinsicSignedExtensions::find),
//! in the returned [signed extensions](crate::blocks::ExtrinsicSignedExtensions).
//!
//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and the
//! [account nonce](crate::blocks::ExtrinsicSignedExtensions::tip()) associated with an extrinsic, given its signed extensions.
//! If you prefer to do things dynamically you can get the data of the signed extension as a [scale value](crate::blocks::ExtrinsicSignedExtension::value()).
//!

0 comments on commit f29fbed

Please sign in to comment.