Skip to content

Commit

Permalink
Make working with nested queries a touch easier (paritytech#714)
Browse files Browse the repository at this point in the history
* First pass adding functions to get blocks and extrinsics

* cargo fmt and cache block events

* prefix block hash with 0x

* pin streams for better ergonomics and add an example of subscribing to blocks

* remove unused var

* standardise on _all, _best and _finalized for different block header subs

* WIP center subscribing around blocks

* Remove the event filtering/subscribing  stuff

* clippy

* we need tokio, silly clippy

* add extrinsic_index() call

* Update subxt/src/blocks/block_types.rs

Co-authored-by: Andrew Jones <[email protected]>

* Add dynbamic nested query example and make dynamic::tx a little easier to work with

* calL_value -> inner_tx

* rename example to dynamic_multisig to align with paritytech#713 naming

* align dynamic and static multisig examples

* Fix comment typo

Co-authored-by: Niklas Adolfsson <[email protected]>

Co-authored-by: Andrew Jones <[email protected]>
Co-authored-by: Niklas Adolfsson <[email protected]>
  • Loading branch information
3 people authored Nov 15, 2022
1 parent 14e8e6f commit 92ace06
Show file tree
Hide file tree
Showing 6 changed files with 2,243 additions and 1,862 deletions.
Binary file modified artifacts/polkadot_metadata.scale
Binary file not shown.
79 changes: 79 additions & 0 deletions examples/examples/dynamic_multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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.

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

use sp_keyring::AccountKeyring;
use subxt::{
dynamic::Value,
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();

// My account.
let signer_account = AccountKeyring::Alice;
let signer_account_id = signer_account.to_account_id();
let signer = PairSigner::new(signer_account.pair());

// Transfer balance to this destination:
let dest = AccountKeyring::Bob.to_account_id();

// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;

// Create the inner balance transfer call.
let inner_tx = subxt::dynamic::tx(
"Balances",
"transfer",
vec![
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
Value::u128(123_456_789_012_345),
],
);

// Now, build an outer call which this inner call will be a part of.
// This sets up the multisig arrangement.
//
// Note: Since this is a dynamic call, we can either use named or unnamed
// arguments (if unnamed, the order matters).
let tx = subxt::dynamic::tx(
"Multisig",
"as_multi",
vec![
("threshold", Value::u128(1)),
(
"other_signatories",
Value::unnamed_composite([Value::from_bytes(&signer_account_id)]),
),
("maybe_timepoint", Value::unnamed_variant("None", [])),
("call", inner_tx.into_value()),
(
"max_weight",
Value::named_composite([
("ref_time", Value::u128(10000000000)),
("proof_size", Value::u128(1)),
]),
),
],
);

// Submit it:
let encoded = hex::encode(&api.tx().call_data(&tx)?);
println!("Call data: {encoded}");
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Submitted tx with hash {tx_hash}");

Ok(())
}
76 changes: 76 additions & 0 deletions examples/examples/multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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.

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

use sp_keyring::AccountKeyring;
use subxt::{
tx::PairSigner,
OnlineClient,
PolkadotConfig,
};

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();

// My account.
let signer_account = AccountKeyring::Alice;
let signer_account_id = signer_account.to_account_id();
let signer = PairSigner::new(signer_account.pair());

// Transfer balance to this destination:
let dest = AccountKeyring::Bob.to_account_id();

// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;

// Create the inner balance transfer call.
//
// Note: This call, being manually constructed, will have a specific pallet and call index
// which is determined by the generated code. If you're trying to submit this to a node which
// has the pallets/calls at different indexes, it will fail. See `dynamic_multisig.rs` for a
// workaround in this case which will work regardless of pallet and call indexes.
let inner_tx = polkadot::runtime_types::polkadot_runtime::RuntimeCall::Balances(
polkadot::runtime_types::pallet_balances::pallet::Call::transfer {
dest: dest.into(),
value: 123_456_789_012_345,
},
);

// Now, build an outer call which this inner call will be a part of.
// This sets up the multisig arrangement.
let tx = polkadot::tx().multisig().as_multi(
// threshold
1,
// other signatories
vec![signer_account_id],
// maybe timepoint
None,
// call
inner_tx,
// max weight
polkadot::runtime_types::sp_weights::weight_v2::Weight {
ref_time: 10000000000,
proof_size: 1,
},
);

// Submit the extrinsic with default params:
let encoded = hex::encode(&api.tx().call_data(&tx)?);
println!("Call data: {encoded}");
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
println!("Submitted tx with hash {tx_hash}");

Ok(())
}
4 changes: 2 additions & 2 deletions subxt/src/tx/tx_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
{
let metadata = self.client.metadata();
let mut bytes = Vec::new();
call.encode_call_data(&metadata, &mut bytes)?;
call.encode_call_data_to(&metadata, &mut bytes)?;
Ok(bytes)
}

Expand All @@ -101,7 +101,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
// transaction protocol version (4) (is not signed, so no 1 bit at the front).
4u8.encode_to(&mut encoded_inner);
// encode call data after this byte.
call.encode_call_data(&self.client.metadata(), &mut encoded_inner)?;
call.encode_call_data_to(&self.client.metadata(), &mut encoded_inner)?;
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
Expand Down
60 changes: 52 additions & 8 deletions subxt/src/tx/tx_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,31 @@ use crate::{
metadata::Metadata,
};
use codec::Encode;
use scale_value::{
Composite,
ValueDef,
Variant,
};
use std::borrow::Cow;

/// This represents a transaction payload that can be submitted
/// to a node.
pub trait TxPayload {
/// Encode call data to the provided output.
fn encode_call_data(
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error>;

/// Encode call data and return the output. This is a convenience
/// wrapper around [`TxPayload::encode_call_data_to`].
fn encode_call_data(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut v = Vec::new();
self.encode_call_data_to(metadata, &mut v)?;
Ok(v)
}

/// Returns the details needed to validate the call, which
/// include a statically generated hash, the pallet name,
/// and the call name.
Expand Down Expand Up @@ -83,7 +96,7 @@ impl<CallData> StaticTxPayload<CallData> {
}

impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
fn encode_call_data(
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
Expand Down Expand Up @@ -113,32 +126,63 @@ impl<CallData: Encode> TxPayload for StaticTxPayload<CallData> {
pub struct DynamicTxPayload<'a> {
pallet_name: Cow<'a, str>,
call_name: Cow<'a, str>,
fields: Vec<Value<()>>,
fields: Composite<()>,
}

impl<'a> DynamicTxPayload<'a> {
/// Return the pallet name.
pub fn pallet_name(&self) -> &str {
&self.pallet_name
}

/// Return the call name.
pub fn call_name(&self) -> &str {
&self.call_name
}

/// Convert the dynamic payload into a [`Value`]. This is useful
/// if you need to submit this as part of a larger call.
pub fn into_value(self) -> Value<()> {
let call = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.into_owned(),
values: self.fields,
}),
};

Value::unnamed_variant(self.pallet_name, [call])
}
}

/// Construct a new dynamic transaction payload to submit to a node.
pub fn dynamic<'a>(
pallet_name: impl Into<Cow<'a, str>>,
call_name: impl Into<Cow<'a, str>>,
fields: Vec<Value<()>>,
fields: impl Into<Composite<()>>,
) -> DynamicTxPayload<'a> {
DynamicTxPayload {
pallet_name: pallet_name.into(),
call_name: call_name.into(),
fields,
fields: fields.into(),
}
}

impl<'a> TxPayload for DynamicTxPayload<'a> {
fn encode_call_data(
fn encode_call_data_to(
&self,
metadata: &Metadata,
out: &mut Vec<u8>,
) -> Result<(), Error> {
let pallet = metadata.pallet(&self.pallet_name)?;
let call_id = pallet.call_ty_id().ok_or(MetadataError::CallNotFound)?;
let call_value =
Value::unnamed_variant(self.call_name.clone(), self.fields.clone());
let call_value = Value {
context: (),
value: ValueDef::Variant(Variant {
name: self.call_name.to_string(),
values: self.fields.clone(),
}),
};

pallet.index().encode_to(out);
scale_value::scale::encode_as_type(&call_value, call_id, metadata.types(), out)?;
Expand Down
Loading

0 comments on commit 92ace06

Please sign in to comment.