From bca3a3b38a238e267775260b7945509924e0976f Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 19 Jul 2022 13:13:58 +0200 Subject: [PATCH] [do-not-merge] Bridge hub sample call with custom ExtrinsicParams --- examples/examples/call_bridge.rs | 97 +++++++++++++++++++++++ subxt/src/extrinsic/mod.rs | 2 + subxt/src/extrinsic/params.rs | 127 +++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 examples/examples/call_bridge.rs diff --git a/examples/examples/call_bridge.rs b/examples/examples/call_bridge.rs new file mode 100644 index 0000000000..cab9fb236a --- /dev/null +++ b/examples/examples/call_bridge.rs @@ -0,0 +1,97 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! ```bash +//! ./target/release/subxt metadata --url ws://localhost:9946 > artifacts/bridge_metadata.scale +//! ``` + +use sp_keyring::AccountKeyring; +use subxt::{ + extrinsic::{ + BridgeHubExtrinsicParams, + BridgeHubExtrinsicParamsBuilder, + }, + ClientBuilder, + DefaultConfig, + PairSigner, +}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/bridge_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + // max_size 50 is pallet's config T::StringMaxLength in runtime, so only 50 will pass + + let result = call_set_name("123456", 51, 1).await; + assert!(result.is_err()); + eprintln!("Result failed as expected: {:?}", result); + + let result = call_set_name("123456", 50, 51).await; + assert!(result.is_err()); + eprintln!("Result failed as expected: {:?}", result); + + let result = call_set_name("12345678910", 50, 1).await; + assert!(result.is_err()); + eprintln!("Result failed as expected: {:?}", result); + + let result = call_set_name("123456789", 50, 1).await; + assert!(result.is_err()); + eprintln!("Result failed as expected: {:?}", result); + + assert!(call_set_name("1234567", 50, 1).await.is_ok()); + assert!(call_set_name("123", 50, 1).await.is_ok()); + + println!("Hurraaa, test passed"); + Ok(()) +} + +/// This is the highest level approach to using this API. We use `wait_for_finalized_success` +/// to wait for the transaction to make it into a finalized block, and also ensure that the +/// transaction was successful according to the associated events. +async fn call_set_name( + data: &str, + max_size: u32, + priority: u8, +) -> Result<(), Box> { + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + + let params = BridgeHubExtrinsicParamsBuilder::new(max_size, priority); + + let api = ClientBuilder::new() + .set_url("ws://127.0.0.1:9946") + .build() + .await? + .to_runtime_api::>>(); + + let data = data.as_bytes().to_vec(); + let data = + polkadot::runtime_types::sp_runtime::bounded::bounded_vec::BoundedVec::(data); + + let result = api + .tx() + .bridge_hub_sample() + .set_name(data)? + .sign_and_submit_then_watch(&signer, params) + .await? + .wait_for_finalized_success() + .await?; + + for (idx, event) in result.iter().enumerate() { + println!("Bridge event ({}): {:?}", idx, event); + } + + let success_event = result + .find_first::() + .expect("decode error"); + + if let Some(event) = success_event { + println!("Bridge success: {:?}", event); + } else { + panic!("Failed to call bridge: {:?}", result); + } + + Ok(()) +} diff --git a/subxt/src/extrinsic/mod.rs b/subxt/src/extrinsic/mod.rs index 6d7b694738..c26b3c8535 100644 --- a/subxt/src/extrinsic/mod.rs +++ b/subxt/src/extrinsic/mod.rs @@ -26,6 +26,8 @@ pub use self::{ AssetTip, BaseExtrinsicParams, BaseExtrinsicParamsBuilder, + BridgeHubExtrinsicParams, + BridgeHubExtrinsicParamsBuilder, Era, ExtrinsicParams, PlainTip, diff --git a/subxt/src/extrinsic/params.rs b/subxt/src/extrinsic/params.rs index 38dedbe1a8..622117aece 100644 --- a/subxt/src/extrinsic/params.rs +++ b/subxt/src/extrinsic/params.rs @@ -56,6 +56,13 @@ pub type SubstrateExtrinsicParams = BaseExtrinsicParams; /// This is what you provide to methods like `sign_and_submit()`. pub type SubstrateExtrinsicParamsBuilder = BaseExtrinsicParamsBuilder; +/// A struct representing the signed extra and additional parameters required +/// to construct a transaction for the default substrate node. +pub type BridgeHubExtrinsicParams = ExtendedExtrinsicParams; +/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed. +/// This is what you provide to methods like `sign_and_submit()`. +pub type BridgeHubExtrinsicParamsBuilder = ExtendedExtrinsicParamsBuilder; + /// A struct representing the signed extra and additional parameters required /// to construct a transaction for a polkadot node. pub type PolkadotExtrinsicParams = BaseExtrinsicParams; @@ -175,6 +182,126 @@ impl ExtrinsicParams for BaseExtrinsicParams< } } +/// An implementation of [`ExtrinsicParams`] that is suitable for constructing +/// extrinsics that can be sent to a node with the same signed extra and additional +/// parameters as a Polkadot/Substrate node. The way that tip payments are specified +/// differs between Substrate and Polkadot nodes, and so we are generic over that in +/// order to support both here with relative ease. +/// +/// If your node differs in the "signed extra" and "additional" parameters expected +/// to be sent/signed with a transaction, then you can define your own type which +/// implements the [`ExtrinsicParams`] trait. +#[derive(Debug)] +pub struct ExtendedExtrinsicParams { + era: Era, + nonce: T::Index, + tip: Tip, + max_size: u32, + priority: u8, + spec_version: u32, + transaction_version: u32, + genesis_hash: T::Hash, + mortality_checkpoint: T::Hash, + marker: std::marker::PhantomData, +} + +/// This builder allows you to provide the parameters that can be configured in order to +/// construct a [`BaseExtrinsicParams`] value. This implements [`Default`], which allows +/// [`BaseExtrinsicParams`] to be used with convenience methods like `sign_and_submit_default()`. +/// +/// Prefer to use [`SubstrateExtrinsicParamsBuilder`] for a version of this tailored towards +/// Substrate, or [`PolkadotExtrinsicParamsBuilder`] for a version tailored to Polkadot. +pub struct ExtendedExtrinsicParamsBuilder { + era: Era, + mortality_checkpoint: Option, + tip: Tip, + max_size: u32, + priority: u8, +} + +impl ExtendedExtrinsicParamsBuilder { + /// Instantiate the default set of [`BaseExtrinsicParamsBuilder`] + pub fn new(max_size: u32, priority: u8) -> Self { + Self { + era: Era::Immortal, + mortality_checkpoint: None, + tip: Tip::default(), + max_size, + priority, + } + } + + /// Set the [`Era`], which defines how long the transaction will be valid for + /// (it can be either immortal, or it can be mortal and expire after a certain amount + /// of time). The second argument is the block hash after which the transaction + /// becomes valid, and must align with the era phase (see the [`Era::Mortal`] docs + /// for more detail on that). + pub fn era(mut self, era: Era, checkpoint: T::Hash) -> Self { + self.era = era; + self.mortality_checkpoint = Some(checkpoint); + self + } + + /// Set the tip you'd like to give to the block author + /// for this transaction. + pub fn tip(mut self, tip: impl Into) -> Self { + self.tip = tip.into(); + self + } + + /// Set the priority + pub fn priority(mut self, priority: u8) -> Self { + self.priority = priority; + self + } +} + +impl ExtrinsicParams for ExtendedExtrinsicParams { + type OtherParams = ExtendedExtrinsicParamsBuilder; + + fn new( + // Provided from subxt client: + spec_version: u32, + transaction_version: u32, + nonce: T::Index, + genesis_hash: T::Hash, + // Provided externally: + other_params: Self::OtherParams, + ) -> Self { + ExtendedExtrinsicParams { + era: other_params.era, + mortality_checkpoint: other_params + .mortality_checkpoint + .unwrap_or(genesis_hash), + tip: other_params.tip, + max_size: other_params.max_size, + priority: other_params.priority, + nonce, + spec_version, + transaction_version, + genesis_hash, + marker: std::marker::PhantomData, + } + } + + fn encode_extra_to(&self, v: &mut Vec) { + let nonce: u64 = self.nonce.into(); + let tip = Encoded(self.tip.encode()); + (self.era, Compact(nonce), tip, self.priority).encode_to(v); + } + + fn encode_additional_to(&self, v: &mut Vec) { + ( + self.spec_version, + self.transaction_version, + self.genesis_hash, + self.mortality_checkpoint, + self.max_size, + ) + .encode_to(v); + } +} + /// A tip payment. #[derive(Copy, Clone, Debug, Default, Encode)] pub struct PlainTip {