diff --git a/examples/examples/block_extrinsics.rs b/examples/examples/block_extrinsics.rs new file mode 100644 index 0000000000..6bde5b0bb5 --- /dev/null +++ b/examples/examples/block_extrinsics.rs @@ -0,0 +1,86 @@ +// 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::blocks::ExtrinsicError; +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> { + tracing_subscriber::fmt::init(); + + // Create a client to use: + let api = OnlineClient::::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. + for extrinsic in block.body().await?.extrinsics() { + println!(" Extrinsic index {:?}", extrinsic.index()); + + let decoded: Result< + polkadot::runtime_types::polkadot_runtime::RuntimeCall, + ExtrinsicError, + > = extrinsic.decode(); + match decoded { + Ok(decoded) => { + println!(" Decoded extrinsic: {:?}", decoded); + } + Err(err) => { + println!(" Decoded extrinsic with error: {:?}", err); + } + } + } + + println!("\n"); + } + + Ok(()) +} diff --git a/subxt/src/blocks/block_types.rs b/subxt/src/blocks/block_types.rs index 3c98e40d39..e27352e74c 100644 --- a/subxt/src/blocks/block_types.rs +++ b/subxt/src/blocks/block_types.rs @@ -11,6 +11,7 @@ use crate::{ runtime_api::RuntimeApi, storage::Storage, }; +use codec::Decode; use derivative::Derivative; use futures::lock::Mutex as AsyncMutex; use std::sync::Arc; @@ -160,6 +161,37 @@ where pub fn bytes(&self) -> &'a [u8] { self.bytes } + + /// Decode the extrinsic to the provided return type. + pub fn decode(&self) -> Result { + const SIGNATURE_MASK: u8 = 0b1000_0000; + const VERSION_MASK: u8 = 0b0111_1111; + const LATEST_EXTRINSIC_VERSION: u8 = 4; + + // Extrinsic are encoded in memory in the following way: + // - Compact: Length of the extrinsic + // - first byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version) + // - signature: emitted `ParaInherent` must be unsigned. + // - extrinsic data + if self.bytes.is_empty() { + return Err(ExtrinsicError::InsufficientData); + } + + let is_signed = self.bytes[0] & SIGNATURE_MASK != 0; + if is_signed { + return Err(ExtrinsicError::SignatureUnsupported); + } + + let version = self.bytes[0] & VERSION_MASK; + if version != LATEST_EXTRINSIC_VERSION { + return Err(ExtrinsicError::UnsupportedVersion(version)); + } + + let mut bytes = Vec::new(); + bytes.extend_from_slice(&self.bytes[1..]); + + Ext::decode(&mut &bytes[..]).map_err(ExtrinsicError::DecodingError) + } } impl<'a, T, C> Extrinsic<'a, T, C> @@ -175,6 +207,19 @@ where } } +/// Error resulted from decoding an extrinsic. +#[derive(Debug)] +pub enum ExtrinsicError { + /// Expected more extrinsic bytes. + InsufficientData, + /// Unsupported signature. + SignatureUnsupported, + /// The extrinsic has an unsupported version. + UnsupportedVersion(u8), + /// Decoding error. + DecodingError(codec::Error), +} + /// The events associated with a given extrinsic. #[derive(Derivative)] #[derivative(Debug(bound = ""))] diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index d60df23d99..c987760ce0 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -7,5 +7,5 @@ mod block_types; mod blocks_client; -pub use block_types::{Block, Extrinsic, ExtrinsicEvents}; +pub use block_types::{Block, Extrinsic, ExtrinsicEvents, ExtrinsicError}; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient};