Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Signed Extension and Block Decoding Examples/Book #1357

Merged
merged 17 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
jsdw marked this conversation as resolved.
Show resolved Hide resolved
// 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())
jsdw marked this conversation as resolved.
Show resolved Hide resolved
//!
//! 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.
jsdw marked this conversation as resolved.
Show resolved Hide resolved
//! 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()).
//!
Loading