Simplify creating and signing extrinsics (#490)

* WIP extrinsic api updates

* First pass done; now to get things compiling again

* document and tweak new structs/traits

* cargo check --all-targets now compiles without issue

* Polkadot and Substrate take different extra params; support both

* Fix transaction format (missing byte from AccountId -> Address) and fmt

* Tweak Signer trait

* Tweak comments and such in extrinsic params

* check all examples against newer polkadot, add new one with params, import path tweaks

* clippy fix, and save an allocation when signing

* Remove unnecessary Default clauses

* Tidy up and fix comments. Panic if payload size >4GB

* fix typo
This commit is contained in:
James Wilson
2022-03-30 18:53:54 +02:00
committed by GitHub
parent 82f304005b
commit 3d669f97c6
32 changed files with 2064 additions and 1114 deletions
+3 -3
View File
@@ -38,13 +38,13 @@ resides ([`CARGO_MANIFEST_DIR`](https://doc.rust-lang.org/cargo/reference/enviro
### Initializing the API client
```rust
use subxt::{ClientBuilder, DefaultConfig, DefaultExtra};
use subxt::{ClientBuilder, DefaultConfig, SubstrateExtrinsicParams};
let api = ClientBuilder::new()
.set_url("wss://rpc.polkadot.io:443")
.build()
.await?
.to_runtime_api::<node_runtime::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<node_runtime::RuntimeApi<DefaultConfig, SubstrateExtrinsicParams<DefaultConfig>>>();
```
The `RuntimeApi` type is generated by the `subxt` macro from the supplied metadata. This can be parameterized with user
@@ -53,7 +53,7 @@ chain.
### Querying Storage
Call the generated `RuntimeApi::storage()` method, followed by the `pallet_name()` and then the `storage_item_name()`.
Call the generated `RuntimeApi::storage()` method, followed by the `pallet_name()` and then the `storage_item_name()`.
So in order to query `Balances::TotalIssuance`:
+1 -1
View File
@@ -116,7 +116,7 @@ pub fn generate_calls(
impl<'a, T, X> TransactionApi<'a, T, X>
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
X: ::subxt::extrinsic::ExtrinsicParams<T>,
{
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
+3 -3
View File
@@ -288,7 +288,7 @@ impl RuntimeGenerator {
impl<T, X> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X>
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>
X: ::subxt::extrinsic::ExtrinsicParams<T>
{
fn from(client: ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
@@ -298,7 +298,7 @@ impl RuntimeGenerator {
impl<'a, T, X> RuntimeApi<T, X>
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
X: ::subxt::extrinsic::ExtrinsicParams<T>,
{
pub fn constants(&'a self) -> ConstantsApi {
ConstantsApi
@@ -368,7 +368,7 @@ impl RuntimeGenerator {
impl<'a, T, X> TransactionApi<'a, T, X>
where
T: ::subxt::Config,
X: ::subxt::SignedExtra<T>,
X: ::subxt::extrinsic::ExtrinsicParams<T>,
{
#(
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X> {
+5 -5
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -26,8 +26,8 @@ use sp_keyring::AccountKeyring;
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PairSigner,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -43,12 +43,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let hash = api
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit(&signer)
.transfer(dest, 123_456_789_012_345)
.sign_and_submit_default(&signer)
.await?;
println!("Balance transfer extrinsic submitted: {}", hash);
@@ -0,0 +1,69 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is part of subxt.
//
// subxt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// subxt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.13/polkadot" --output /usr/local/bin/polkadot --location
//! polkadot --dev --tmp
//! ```
use sp_keyring::AccountKeyring;
use subxt::{
extrinsic::{
Era,
PlainTip,
},
ClientBuilder,
DefaultConfig,
PairSigner,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder as Params,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
pub mod polkadot {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Configure the transaction tip and era:
let tx_params = Params::new()
.tip(PlainTip::new(20_000_000_000))
.era(Era::Immortal, *api.client.genesis());
// Send the transaction:
let hash = api
.tx()
.balances()
.transfer(dest, 123_456_789_012_345)
.sign_and_submit(&signer, tx_params)
.await?;
println!("Balance transfer extrinsic submitted: {}", hash);
Ok(())
}
+4 -4
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -27,8 +27,8 @@ use subxt::{
ClientBuilder,
Config,
DefaultConfig,
DefaultExtra,
PairSigner,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -60,7 +60,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<MyConfig, DefaultExtra<MyConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<MyConfig, PolkadotExtrinsicParams<MyConfig>>>();
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let dest = AccountKeyring::Bob.to_account_id().into();
@@ -69,7 +69,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit(&signer)
.sign_and_submit_default(&signer)
.await?;
println!("Balance transfer extrinsic submitted: {}", hash);
+1 -1
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
#![allow(clippy::redundant_clone)]
+3 -3
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -25,7 +25,7 @@
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -38,7 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let mut iter = api.storage().system().account_iter(None).await?;
+2 -2
View File
@@ -31,7 +31,7 @@ use subxt::{
sp_runtime::AccountId32,
ClientBuilder,
DefaultConfig,
DefaultExtra,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -44,7 +44,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let era = api.storage().staking().active_era(None).await?.unwrap();
println!(
Binary file not shown.
+3 -3
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -25,7 +25,7 @@
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -39,7 +39,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.set_url("wss://rpc.polkadot.io:443")
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let block_number = 1u32;
+8 -8
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -27,8 +27,8 @@ use sp_keyring::AccountKeyring;
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PairSigner,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -55,13 +55,13 @@ async fn simple_transfer() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<_>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let balance_transfer = api
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit_then_watch(&signer)
.sign_and_submit_then_watch_default(&signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -87,13 +87,13 @@ async fn simple_transfer_separate_events() -> Result<(), Box<dyn std::error::Err
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<_>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let balance_transfer = api
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit_then_watch(&signer)
.sign_and_submit_then_watch_default(&signer)
.await?
.wait_for_finalized()
.await?;
@@ -138,13 +138,13 @@ async fn handle_transfer_events() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<_>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let mut balance_transfer_progress = api
.tx()
.balances()
.transfer(dest, 10_000)
.sign_and_submit_then_watch(&signer)
.sign_and_submit_then_watch_default(&signer)
.await?;
while let Some(ev) = balance_transfer_progress.next().await {
+13 -9
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -28,8 +28,8 @@ use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PairSigner,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -44,7 +44,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Subscribe to any events that occur:
let mut event_sub = api.events().subscribe().await?;
@@ -52,11 +52,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// While this subscription is active, balance transfers are made somewhere:
async_std::task::spawn(async {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api = ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
let api =
ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<
DefaultConfig,
PolkadotExtrinsicParams<DefaultConfig>,
>>();
let mut transfer_amount = 1_000_000_000;
@@ -65,7 +69,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
api.tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount)
.sign_and_submit(&signer)
.sign_and_submit_default(&signer)
.await
.unwrap();
+13 -9
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -28,8 +28,8 @@ use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PairSigner,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -45,7 +45,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Subscribe to just balance transfer events, making use of `filter_events`
// to select a single event type (note the 1-tuple) to filter out and return.
@@ -58,18 +58,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// While this subscription is active, we imagine some balance transfers are made somewhere else:
async_std::task::spawn(async {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api = ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
let api =
ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<
DefaultConfig,
PolkadotExtrinsicParams<DefaultConfig>,
>>();
// Make small balance transfers from Alice to Bob in a loop:
loop {
api.tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), 1_000_000_000)
.sign_and_submit(&signer)
.sign_and_submit_default(&signer)
.await
.unwrap();
async_std::task::sleep(Duration::from_secs(10)).await;
+13 -9
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos.
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.18-f6d6ab005d-aarch64-macos.
//!
//! E.g.
//! ```bash
@@ -28,8 +28,8 @@ use std::time::Duration;
use subxt::{
ClientBuilder,
DefaultConfig,
DefaultExtra,
PairSigner,
PolkadotExtrinsicParams,
};
#[subxt::subxt(runtime_metadata_path = "examples/polkadot_metadata.scale")]
@@ -45,7 +45,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
// Subscribe to several balance related events. If we ask for more than one event,
// we'll be given a correpsonding tuple of `Option`'s, with exactly one
@@ -59,18 +59,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// While this subscription is active, we imagine some balance transfers are made somewhere else:
async_std::task::spawn(async {
let signer = PairSigner::new(AccountKeyring::Alice.pair());
let api = ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>>();
let api =
ClientBuilder::new()
.build()
.await
.unwrap()
.to_runtime_api::<polkadot::RuntimeApi<
DefaultConfig,
PolkadotExtrinsicParams<DefaultConfig>,
>>();
// Make small balance transfers from Alice to Bob in a loop:
loop {
api.tx()
.balances()
.transfer(AccountKeyring::Bob.to_account_id().into(), 1_000_000_000)
.sign_and_submit(&signer)
.sign_and_submit_default(&signer)
.await
.unwrap();
async_std::task::sleep(Duration::from_secs(10)).await;
+119 -41
View File
@@ -24,10 +24,8 @@ use crate::{
HasModuleError,
},
extrinsic::{
self,
SignedExtra,
ExtrinsicParams,
Signer,
UncheckedExtrinsic,
},
rpc::{
Rpc,
@@ -39,9 +37,14 @@ use crate::{
transaction::TransactionProgress,
Call,
Config,
Encoded,
Metadata,
};
use codec::Decode;
use codec::{
Compact,
Decode,
Encode,
};
use derivative::Derivative;
use std::sync::Arc;
@@ -188,7 +191,7 @@ pub struct SubmittableExtrinsic<'client, T: Config, X, C, E: Decode, Evs: Decode
impl<'client, T, X, C, E, Evs> SubmittableExtrinsic<'client, T, X, C, E, Evs>
where
T: Config,
X: SignedExtra<T>,
X: ExtrinsicParams<T>,
C: Call + Send + Sync,
E: Decode + HasModuleError,
Evs: Decode,
@@ -202,20 +205,33 @@ where
}
}
/// Creates and signs an extrinsic and submits it to the chain. Passes default parameters
/// to construct the "signed extra" and "additional" payloads needed by the extrinsic.
///
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch_default(
self,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError>
where
X::OtherParams: Default,
{
self.sign_and_submit_then_watch(signer, Default::default())
.await
}
/// Creates and signs an extrinsic and submits it to the chain.
///
/// Returns a [`TransactionProgress`], which can be used to track the status of the transaction
/// and obtain details about it, once it has made it into a block.
pub async fn sign_and_submit_then_watch(
self,
signer: &(dyn Signer<T, X> + Send + Sync),
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError>
where
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
Send + Sync + 'static,
{
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<TransactionProgress<'client, T, E, Evs>, BasicError> {
// Sign the call data to create our extrinsic.
let extrinsic = self.create_signed(signer, Default::default()).await?;
let extrinsic = self.create_signed(signer, other_params).await?;
// Get a hash of the extrinsic (we'll need this later).
let ext_hash = T::Hashing::hash_of(&extrinsic);
@@ -226,6 +242,26 @@ where
Ok(TransactionProgress::new(sub, self.client, ext_hash))
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion. Passes
/// default parameters to construct the "signed extra" and "additional" payloads needed
/// by the extrinsic.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
///
/// # Note
///
/// Success does not mean the extrinsic has been included in the block, just that it is valid
/// and has been included in the transaction pool.
pub async fn sign_and_submit_default(
self,
signer: &(dyn Signer<T> + Send + Sync),
) -> Result<T::Hash, BasicError>
where
X::OtherParams: Default,
{
self.sign_and_submit(signer, Default::default()).await
}
/// Creates and signs an extrinsic and submits to the chain for block inclusion.
///
/// Returns `Ok` with the extrinsic hash if it is valid extrinsic.
@@ -236,26 +272,20 @@ where
/// and has been included in the transaction pool.
pub async fn sign_and_submit(
self,
signer: &(dyn Signer<T, X> + Send + Sync),
) -> Result<T::Hash, BasicError>
where
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
Send + Sync + 'static,
{
let extrinsic = self.create_signed(signer, Default::default()).await?;
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<T::Hash, BasicError> {
let extrinsic = self.create_signed(signer, other_params).await?;
self.client.rpc().submit_extrinsic(extrinsic).await
}
/// Creates a signed extrinsic.
/// Creates a returns a raw signed extrinsic, without submitting it.
pub async fn create_signed(
&self,
signer: &(dyn Signer<T, X> + Send + Sync),
additional_params: X::Parameters,
) -> Result<UncheckedExtrinsic<T, X>, BasicError>
where
<<X as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
Send + Sync + 'static,
{
signer: &(dyn Signer<T> + Send + Sync),
other_params: X::OtherParams,
) -> Result<Encoded, BasicError> {
// 1. Get nonce
let account_nonce = if let Some(nonce) = signer.nonce() {
nonce
} else {
@@ -264,21 +294,69 @@ where
.system_account_next_index(signer.account_id())
.await?
};
let call = self
.client
.metadata()
.pallet(C::PALLET)
.and_then(|pallet| pallet.encode_call(&self.call))?;
let signed = extrinsic::create_signed(
&self.client.runtime_version,
self.client.genesis_hash,
// 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
let call_data = {
let mut bytes = Vec::new();
let pallet = self.client.metadata().pallet(C::PALLET)?;
bytes.push(pallet.index());
bytes.push(pallet.call_index::<C>()?);
self.call.encode_to(&mut bytes);
Encoded(bytes)
};
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = X::new(
self.client.runtime_version.spec_version,
self.client.runtime_version.transaction_version,
account_nonce,
call,
signer,
additional_params,
)
.await?;
Ok(signed)
self.client.genesis_hash,
other_params,
);
// 4. Construct signature. This is compatible with the Encode impl
// for SignedPayload (which is this payload of bytes that we'd like)
// to sign. See:
// https://github.com/paritytech/substrate/blob/9a6d706d8db00abb6ba183839ec98ecd9924b1f8/primitives/runtime/src/generic/unchecked_extrinsic.rs#L215)
let signature = {
let mut bytes = Vec::new();
call_data.encode_to(&mut bytes);
additional_and_extra_params.encode_extra_to(&mut bytes);
additional_and_extra_params.encode_additional_to(&mut bytes);
if bytes.len() > 256 {
signer.sign(&sp_core::blake2_256(&bytes))
} else {
signer.sign(&bytes)
}
};
// 5. Encode extrinsic, now that we have the parts we need. This is compatible
// with the Encode impl for UncheckedExtrinsic (protocol version 4).
let extrinsic = {
let mut encoded_inner = Vec::new();
// "is signed" + transaction protocol version (4)
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
// from address for signature
signer.address().encode_to(&mut encoded_inner);
// the signature bytes
signature.encode_to(&mut encoded_inner);
// attach custom extra params
additional_and_extra_params.encode_extra_to(&mut encoded_inner);
// and now, call data
call_data.encode_to(&mut encoded_inner);
// now, prefix byte length:
let len = Compact(
u32::try_from(encoded_inner.len())
.expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
// Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
// maybe we can just return the raw bytes..
Ok(Encoded(extrinsic))
}
}
+2 -1
View File
@@ -43,7 +43,8 @@ pub trait Config: 'static {
+ Default
+ AtLeast32Bit
+ Copy
+ scale_info::TypeInfo;
+ scale_info::TypeInfo
+ Into<u64>;
/// The block number type used by the runtime.
type BlockNumber: Parameter
-481
View File
@@ -1,481 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is part of subxt.
//
// subxt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// subxt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
use crate::PhantomDataSendSync;
use codec::{
Decode,
Encode,
};
use derivative::Derivative;
use scale_info::TypeInfo;
use sp_runtime::{
generic::Era,
traits::{
DispatchInfoOf,
SignedExtension,
},
transaction_validity::TransactionValidityError,
};
use crate::Config;
/// Extra type.
// pub type Extra<T> = <<T as Config>::Extra as SignedExtra<T>>::Extra;
/// SignedExtra checks copied from substrate, in order to remove requirement to implement
/// substrate's `frame_system::Trait`
/// Ensure the runtime version registered in the transaction is the same as at present.
///
/// # Note
///
/// This is modified from the substrate version to allow passing in of the version, which is
/// returned via `additional_signed()`.
/// Ensure the runtime version registered in the transaction is the same as at present.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct CheckSpecVersion<T: Config>(
pub PhantomDataSendSync<T>,
/// Local version to be used for `AdditionalSigned`
#[codec(skip)]
pub u32,
);
impl<T: Config> SignedExtension for CheckSpecVersion<T> {
const IDENTIFIER: &'static str = "CheckSpecVersion";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = u32;
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(self.1)
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Ensure the transaction version registered in the transaction is the same as at present.
///
/// # Note
///
/// This is modified from the substrate version to allow passing in of the version, which is
/// returned via `additional_signed()`.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct CheckTxVersion<T: Config>(
pub PhantomDataSendSync<T>,
/// Local version to be used for `AdditionalSigned`
#[codec(skip)]
pub u32,
);
impl<T: Config> SignedExtension for CheckTxVersion<T> {
const IDENTIFIER: &'static str = "CheckTxVersion";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = u32;
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(self.1)
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Check genesis hash
///
/// # Note
///
/// This is modified from the substrate version to allow passing in of the genesis hash, which is
/// returned via `additional_signed()`.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct CheckGenesis<T: Config>(
pub PhantomDataSendSync<T>,
/// Local genesis hash to be used for `AdditionalSigned`
#[codec(skip)]
pub T::Hash,
);
impl<T: Config> SignedExtension for CheckGenesis<T> {
const IDENTIFIER: &'static str = "CheckGenesis";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = T::Hash;
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(self.1)
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Check for transaction mortality.
///
/// # Note
///
/// This is modified from the substrate version to allow passing in of the genesis hash, which is
/// returned via `additional_signed()`. It assumes therefore `Era::Immortal` (The transaction is
/// valid forever)
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct CheckMortality<T: Config>(
/// The default structure for the Extra encoding
pub (Era, PhantomDataSendSync<T>),
/// Local genesis hash to be used for `AdditionalSigned`
#[codec(skip)]
pub T::Hash,
);
impl<T: Config> SignedExtension for CheckMortality<T> {
const IDENTIFIER: &'static str = "CheckMortality";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = T::Hash;
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(self.1)
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Nonce check and increment to give replay protection for transactions.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct CheckNonce<T: Config>(#[codec(compact)] pub T::Index);
impl<T: Config> SignedExtension for CheckNonce<T> {
const IDENTIFIER: &'static str = "CheckNonce";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Resource limit check.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct CheckWeight<T: Config>(pub PhantomDataSendSync<T>);
impl<T: Config> SignedExtension for CheckWeight<T> {
const IDENTIFIER: &'static str = "CheckWeight";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
/// in the queue.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
Default(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct ChargeTransactionPayment<T: Config>(
#[codec(compact)] u128,
pub PhantomDataSendSync<T>,
);
impl<T: Config> SignedExtension for ChargeTransactionPayment<T> {
const IDENTIFIER: &'static str = "ChargeTransactionPayment";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
/// in the queue.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = ""),
Default(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct ChargeAssetTxPayment<T: Config> {
/// The tip for the block author.
#[codec(compact)]
pub tip: u128,
/// The asset with which to pay the tip.
pub asset_id: Option<u32>,
/// Marker for unused type parameter.
pub marker: PhantomDataSendSync<T>,
}
impl<T: Config> SignedExtension for ChargeAssetTxPayment<T> {
const IDENTIFIER: &'static str = "ChargeAssetTxPayment";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned = ();
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// Trait for implementing transaction extras for a runtime.
pub trait SignedExtra<T: Config>: SignedExtension {
/// The type the extras.
type Extra: SignedExtension + Send + Sync;
/// The additional config parameters.
type Parameters: Default + Send + Sync;
/// Creates a new `SignedExtra`.
fn new(
spec_version: u32,
tx_version: u32,
nonce: T::Index,
genesis_hash: T::Hash,
additional_params: Self::Parameters,
) -> Self;
/// Returns the transaction extra.
fn extra(&self) -> Self::Extra;
}
/// Default `SignedExtra` for substrate runtimes.
#[derive(Derivative, Encode, Decode, TypeInfo)]
#[derivative(
Clone(bound = ""),
PartialEq(bound = ""),
Debug(bound = ""),
Eq(bound = "")
)]
#[scale_info(skip_type_params(T))]
pub struct DefaultExtraWithTxPayment<T: Config, X> {
spec_version: u32,
tx_version: u32,
nonce: T::Index,
genesis_hash: T::Hash,
marker: PhantomDataSendSync<X>,
}
impl<T, X> SignedExtra<T> for DefaultExtraWithTxPayment<T, X>
where
T: Config,
X: SignedExtension<AccountId = T::AccountId, Call = ()> + Default,
{
type Extra = (
CheckSpecVersion<T>,
CheckTxVersion<T>,
CheckGenesis<T>,
CheckMortality<T>,
CheckNonce<T>,
CheckWeight<T>,
X,
);
type Parameters = ();
fn new(
spec_version: u32,
tx_version: u32,
nonce: T::Index,
genesis_hash: T::Hash,
_params: Self::Parameters,
) -> Self {
DefaultExtraWithTxPayment {
spec_version,
tx_version,
nonce,
genesis_hash,
marker: PhantomDataSendSync::new(),
}
}
fn extra(&self) -> Self::Extra {
(
CheckSpecVersion(PhantomDataSendSync::new(), self.spec_version),
CheckTxVersion(PhantomDataSendSync::new(), self.tx_version),
CheckGenesis(PhantomDataSendSync::new(), self.genesis_hash),
CheckMortality(
(Era::Immortal, PhantomDataSendSync::new()),
self.genesis_hash,
),
CheckNonce(self.nonce),
CheckWeight(PhantomDataSendSync::new()),
X::default(),
)
}
}
impl<T, X: SignedExtension<AccountId = T::AccountId, Call = ()> + Default> SignedExtension
for DefaultExtraWithTxPayment<T, X>
where
T: Config,
X: SignedExtension,
{
const IDENTIFIER: &'static str = "DefaultExtra";
type AccountId = T::AccountId;
type Call = ();
type AdditionalSigned =
<<Self as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned;
type Pre = ();
fn additional_signed(
&self,
) -> Result<Self::AdditionalSigned, TransactionValidityError> {
self.extra().additional_signed()
}
fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
}
/// A default `SignedExtra` configuration, with [`ChargeTransactionPayment`] for tipping.
///
/// Note that this must match the `SignedExtra` type in the target runtime's extrinsic definition.
pub type DefaultExtra<T> = DefaultExtraWithTxPayment<T, ChargeTransactionPayment<T>>;
+10 -62
View File
@@ -16,74 +16,22 @@
//! Create signed or unsigned extrinsics.
mod extra;
mod params;
mod signer;
pub use self::{
extra::{
ChargeAssetTxPayment,
ChargeTransactionPayment,
CheckGenesis,
CheckMortality,
CheckNonce,
CheckSpecVersion,
CheckTxVersion,
CheckWeight,
DefaultExtra,
DefaultExtraWithTxPayment,
SignedExtra,
params::{
AssetTip,
Era,
ExtrinsicParams,
PlainTip,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder,
SubstrateExtrinsicParams,
SubstrateExtrinsicParamsBuilder,
},
signer::{
PairSigner,
Signer,
},
};
use sp_runtime::traits::SignedExtension;
use crate::{
error::BasicError,
rpc::RuntimeVersion,
Config,
Encoded,
};
/// UncheckedExtrinsic type.
pub type UncheckedExtrinsic<T, X> = sp_runtime::generic::UncheckedExtrinsic<
<T as Config>::Address,
Encoded,
<T as Config>::Signature,
<X as SignedExtra<T>>::Extra,
>;
/// SignedPayload type.
pub type SignedPayload<T, X> =
sp_runtime::generic::SignedPayload<Encoded, <X as SignedExtra<T>>::Extra>;
/// Creates a signed extrinsic
pub async fn create_signed<T, X>(
runtime_version: &RuntimeVersion,
genesis_hash: T::Hash,
nonce: T::Index,
call: Encoded,
signer: &(dyn Signer<T, X> + Send + Sync),
additional_params: X::Parameters,
) -> Result<UncheckedExtrinsic<T, X>, BasicError>
where
T: Config,
X: SignedExtra<T>,
<X::Extra as SignedExtension>::AdditionalSigned: Send + Sync,
{
let spec_version = runtime_version.spec_version;
let tx_version = runtime_version.transaction_version;
let extra = X::new(
spec_version,
tx_version,
nonce,
genesis_hash,
additional_params,
);
let payload = SignedPayload::<T, X>::new(call, extra.extra())?;
let signed = signer.sign(payload).await?;
Ok(signed)
}
+237
View File
@@ -0,0 +1,237 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is part of subxt.
//
// subxt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// subxt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
use codec::{
Compact,
Encode,
};
use crate::{
Config,
Encoded,
};
// We require Era as a param below, so make it available from here.
pub use sp_runtime::generic::Era;
/// This trait allows you to configure the "signed extra" and
/// "additional" parameters that are signed and used in transactions.
/// see [`BaseExtrinsicParams`] for an implementation that is compatible with
/// a Polkadot node.
pub trait ExtrinsicParams<T: Config> {
/// These parameters can be provided to the constructor along with
/// some default parameters that `subxt` understands, in order to
/// help construct your [`ExtrinsicParams`] object.
type OtherParams;
/// Construct a new instance of our [`ExtrinsicParams`]
fn new(
spec_version: u32,
tx_version: u32,
nonce: T::Index,
genesis_hash: T::Hash,
other_params: Self::OtherParams,
) -> Self;
/// This is expected to SCALE encode the "signed extra" parameters
/// to some buffer that has been provided. These are the parameters
/// which are sent along with the transaction, as well as taken into
/// account when signing the transaction.
fn encode_extra_to(&self, v: &mut Vec<u8>);
/// This is expected to SCALE encode the "additional" parameters
/// to some buffer that has been provided. These parameters are _not_
/// sent along with the transaction, but are taken into account when
/// signing it, meaning the client and node must agree on their values.
fn encode_additional_to(&self, v: &mut Vec<u8>);
}
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for the default substrate node.
pub type SubstrateExtrinsicParams<T> = BaseExtrinsicParams<T, AssetTip>;
/// A builder which leads to [`SubstrateExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type SubstrateExtrinsicParamsBuilder<T> = BaseExtrinsicParamsBuilder<T, AssetTip>;
/// A struct representing the signed extra and additional parameters required
/// to construct a transaction for a polkadot node.
pub type PolkadotExtrinsicParams<T> = BaseExtrinsicParams<T, PlainTip>;
/// A builder which leads to [`PolkadotExtrinsicParams`] being constructed.
/// This is what you provide to methods like `sign_and_submit()`.
pub type PolkadotExtrinsicParamsBuilder<T> = BaseExtrinsicParamsBuilder<T, PlainTip>;
/// 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.
pub struct BaseExtrinsicParams<T: Config, Tip> {
era: Era,
nonce: T::Index,
tip: Tip,
spec_version: u32,
transaction_version: u32,
genesis_hash: T::Hash,
mortality_checkpoint: T::Hash,
marker: std::marker::PhantomData<T>,
}
/// 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 BaseExtrinsicParamsBuilder<T: Config, Tip> {
era: Era,
mortality_checkpoint: Option<T::Hash>,
tip: Tip,
}
impl<T: Config, Tip: Default> BaseExtrinsicParamsBuilder<T, Tip> {
/// Instantiate the default set of [`BaseExtrinsicParamsBuilder`]
pub fn new() -> Self {
Self::default()
}
/// 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<Tip>) -> Self {
self.tip = tip.into();
self
}
}
impl<T: Config, Tip: Default> Default for BaseExtrinsicParamsBuilder<T, Tip> {
fn default() -> Self {
Self {
era: Era::Immortal,
mortality_checkpoint: None,
tip: Tip::default(),
}
}
}
impl<T: Config, Tip: Encode> ExtrinsicParams<T> for BaseExtrinsicParams<T, Tip> {
type OtherParams = BaseExtrinsicParamsBuilder<T, Tip>;
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 {
BaseExtrinsicParams {
era: other_params.era,
mortality_checkpoint: other_params
.mortality_checkpoint
.unwrap_or(genesis_hash),
tip: other_params.tip,
nonce,
spec_version,
transaction_version,
genesis_hash,
marker: std::marker::PhantomData,
}
}
fn encode_extra_to(&self, v: &mut Vec<u8>) {
let nonce: u64 = self.nonce.into();
let tip = Encoded(self.tip.encode());
(self.era, Compact(nonce), tip).encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
(
self.spec_version,
self.transaction_version,
self.genesis_hash,
self.mortality_checkpoint,
)
.encode_to(v);
}
}
/// A tip payment.
#[derive(Copy, Clone, Default, Encode)]
pub struct PlainTip {
#[codec(compact)]
tip: u128,
}
impl PlainTip {
/// Create a new tip of the amount provided.
pub fn new(amount: u128) -> Self {
PlainTip { tip: amount }
}
}
impl From<u128> for PlainTip {
fn from(n: u128) -> Self {
PlainTip::new(n)
}
}
/// A tip payment made in the form of a specific asset.
#[derive(Copy, Clone, Default, Encode)]
pub struct AssetTip {
#[codec(compact)]
tip: u128,
asset: Option<u32>,
}
impl AssetTip {
/// Create a new tip of the amount provided.
pub fn new(amount: u128) -> Self {
AssetTip {
tip: amount,
asset: None,
}
}
/// Designate the tip as being of a particular asset class.
/// If this is not set, then the native currency is used.
pub fn of_asset(mut self, asset: u32) -> Self {
self.asset = Some(asset);
self
}
}
impl From<u128> for AssetTip {
fn from(n: u128) -> Self {
AssetTip::new(n)
}
}
+37 -50
View File
@@ -17,58 +17,51 @@
//! A library to **sub**mit e**xt**rinsics to a
//! [substrate](https://github.com/paritytech/substrate) node via RPC.
use super::{
SignedExtra,
SignedPayload,
UncheckedExtrinsic,
};
use crate::Config;
use codec::Encode;
use sp_core::Pair;
use sp_runtime::traits::{
IdentifyAccount,
SignedExtension,
Verify,
};
/// Extrinsic signer.
#[async_trait::async_trait]
pub trait Signer<T: Config, E: SignedExtra<T>> {
/// Returns the account id.
fn account_id(&self) -> &T::AccountId;
/// Signing transactions requires a [`Signer`]. This is responsible for
/// providing the "from" account that the transaction is being signed by,
/// as well as actually signing a SCALE encoded payload. Optionally, a
/// signer can also provide the nonce for the transaction to use.
pub trait Signer<T: Config> {
/// Optionally returns a nonce.
fn nonce(&self) -> Option<T::Index>;
/// Takes an unsigned extrinsic and returns a signed extrinsic.
/// Return the "from" account ID.
fn account_id(&self) -> &T::AccountId;
/// Return the "from" address.
fn address(&self) -> T::Address;
/// Takes a signer payload for an extrinsic, and returns a signature based on it.
///
/// Some signers may fail, for instance because the hardware on which the keys are located has
/// refused the operation.
async fn sign(
&self,
extrinsic: SignedPayload<T, E>,
) -> Result<UncheckedExtrinsic<T, E>, String>;
fn sign(&self, signer_payload: &[u8]) -> T::Signature;
}
/// Extrinsic signer using a private key.
/// A [`Signer`] implementation that can be constructed from an [`Pair`].
#[derive(Clone, Debug)]
pub struct PairSigner<T: Config, E, P: Pair> {
pub struct PairSigner<T: Config, P: Pair> {
account_id: T::AccountId,
nonce: Option<T::Index>,
signer: P,
marker: std::marker::PhantomData<E>,
}
impl<T, E, P> PairSigner<T, E, P>
impl<T, P> PairSigner<T, P>
where
T: Config,
E: SignedExtra<T>,
T::Signature: From<P::Signature>,
<T::Signature as Verify>::Signer:
From<P::Public> + IdentifyAccount<AccountId = T::AccountId>,
P: Pair,
{
/// Creates a new `Signer` from a `Pair`.
/// Creates a new [`Signer`] from a [`Pair`].
pub fn new(signer: P) -> Self {
let account_id =
<T::Signature as Verify>::Signer::from(signer.public()).into_account();
@@ -76,11 +69,11 @@ where
account_id,
nonce: None,
signer,
marker: Default::default(),
}
}
/// Sets the nonce to a new value.
/// Sets the nonce to a new value. By default, the nonce will
/// be retrieved from the node. Setting one here will override that.
pub fn set_nonce(&mut self, nonce: T::Index) {
self.nonce = Some(nonce);
}
@@ -90,43 +83,37 @@ where
self.nonce = self.nonce.map(|nonce| nonce + 1u32.into());
}
/// Returns the signer.
/// Returns the [`Pair`] implementation used to construct this.
pub fn signer(&self) -> &P {
&self.signer
}
/// Return the account ID.
pub fn account_id(&self) -> &T::AccountId {
&self.account_id
}
}
#[async_trait::async_trait]
impl<T, E, P> Signer<T, E> for PairSigner<T, E, P>
impl<T, P> Signer<T> for PairSigner<T, P>
where
T: Config,
E: SignedExtra<T>,
T::AccountId: Into<T::Address> + 'static,
<<E as SignedExtra<T>>::Extra as SignedExtension>::AdditionalSigned:
Send + Sync + 'static,
T::AccountId: Into<T::Address> + Clone + 'static,
P: Pair + 'static,
P::Signature: Into<T::Signature> + 'static,
{
fn account_id(&self) -> &T::AccountId {
&self.account_id
}
fn nonce(&self) -> Option<T::Index> {
self.nonce
}
async fn sign(
&self,
extrinsic: SignedPayload<T, E>,
) -> Result<UncheckedExtrinsic<T, E>, String> {
let signature = extrinsic.using_encoded(|payload| self.signer.sign(payload));
let (call, extra, _) = extrinsic.deconstruct();
let extrinsic = UncheckedExtrinsic::<T, E>::new_signed(
call,
self.account_id.clone().into(),
signature.into(),
extra,
);
Ok(extrinsic)
fn account_id(&self) -> &T::AccountId {
&self.account_id
}
fn address(&self) -> T::Address {
self.account_id.clone().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.signer.sign(signer_payload).into()
}
}
+4 -5
View File
@@ -90,12 +90,11 @@ pub use crate::{
RawEventDetails,
},
extrinsic::{
DefaultExtra,
DefaultExtraWithTxPayment,
PairSigner,
SignedExtra,
Signer,
UncheckedExtrinsic,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder,
SubstrateExtrinsicParams,
SubstrateExtrinsicParamsBuilder,
},
metadata::{
ErrorMetadata,
+11 -10
View File
@@ -30,10 +30,7 @@ use frame_metadata::{
META_RESERVED,
};
use crate::{
Call,
Encoded,
};
use crate::Call;
use scale_info::{
form::PortableForm,
Type,
@@ -148,18 +145,22 @@ impl PalletMetadata {
&self.name
}
/// Encode a call based on this pallet metadata.
pub fn encode_call<C>(&self, call: &C) -> Result<Encoded, MetadataError>
/// Get the index of this pallet.
pub fn index(&self) -> u8 {
self.index
}
/// Attempt to resolve a call into an index in this pallet, failing
/// if the call is not found in this pallet.
pub fn call_index<C>(&self) -> Result<u8, MetadataError>
where
C: Call,
{
let fn_index = self
let fn_index = *self
.calls
.get(C::FUNCTION)
.ok_or(MetadataError::CallNotFound(C::FUNCTION))?;
let mut bytes = vec![self.index, *fn_index];
bytes.extend(call.encode());
Ok(Encoded(bytes))
Ok(fn_index)
}
/// Return [`StorageEntryMetadata`] given some storage key.
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -24,7 +24,6 @@ use crate::{
};
use futures::StreamExt;
use sp_keyring::AccountKeyring;
use subxt::Signer;
// Check that we can subscribe to non-finalized block events.
#[async_std::test]
@@ -118,7 +117,7 @@ async fn balance_transfer_subscription() -> Result<(), subxt::BasicError> {
.tx()
.balances()
.transfer(bob.clone().into(), 10_000)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?;
// Wait for the next balance transfer event in our subscription stream
+7 -10
View File
@@ -34,10 +34,7 @@ use sp_runtime::{
AccountId32,
MultiAddress,
};
use subxt::{
Error,
Signer,
};
use subxt::Error;
#[async_std::test]
async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
@@ -62,7 +59,7 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error<DispatchError>> {
.tx()
.balances()
.transfer(bob_address, 10_000)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await?;
@@ -118,7 +115,7 @@ async fn multiple_transfers_work_nonce_incremented(
.tx()
.balances()
.transfer(bob_address.clone(), 10_000)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_in_block() // Don't need to wait for finalization; this is quicker.
.await?
@@ -163,7 +160,7 @@ async fn storage_balance_lock() -> Result<(), subxt::Error<DispatchError>> {
100_000_000_000_000,
runtime_types::pallet_staking::RewardDestination::Stash,
)
.sign_and_submit_then_watch(&bob)
.sign_and_submit_then_watch_default(&bob)
.await?
.wait_for_finalized_success()
.await?
@@ -203,7 +200,7 @@ async fn transfer_error() {
.tx()
.balances()
.transfer(hans_address, 100_000_000_000_000_000)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -215,7 +212,7 @@ async fn transfer_error() {
.tx()
.balances()
.transfer(alice_addr, 100_000_000_000_000_000)
.sign_and_submit_then_watch(&hans)
.sign_and_submit_then_watch_default(&hans)
.await
.unwrap()
.wait_for_finalized_success()
@@ -242,7 +239,7 @@ async fn transfer_implicit_subscription() {
.tx()
.balances()
.transfer(bob_addr, 10_000)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await
.unwrap()
.wait_for_finalized_success()
+6 -6
View File
@@ -28,7 +28,7 @@ use crate::{
DispatchError,
},
test_context,
NodeRuntimeSignedExtra,
NodeRuntimeParams,
TestContext,
};
use sp_core::sr25519::Pair;
@@ -44,7 +44,7 @@ use subxt::{
struct ContractsTestContext {
cxt: TestContext,
signer: PairSigner<DefaultConfig, NodeRuntimeSignedExtra, Pair>,
signer: PairSigner<DefaultConfig, Pair>,
}
type Hash = <DefaultConfig as Config>::Hash;
@@ -62,7 +62,7 @@ impl ContractsTestContext {
self.cxt.client()
}
fn contracts_tx(&self) -> TransactionApi<DefaultConfig, NodeRuntimeSignedExtra> {
fn contracts_tx(&self) -> TransactionApi<DefaultConfig, NodeRuntimeParams> {
self.cxt.api.tx().contracts()
}
@@ -91,7 +91,7 @@ impl ContractsTestContext {
vec![], // data
vec![], // salt
)
.sign_and_submit_then_watch(&self.signer)
.sign_and_submit_then_watch_default(&self.signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -131,7 +131,7 @@ impl ContractsTestContext {
data,
salt,
)
.sign_and_submit_then_watch(&self.signer)
.sign_and_submit_then_watch_default(&self.signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -162,7 +162,7 @@ impl ContractsTestContext {
None, // storage_deposit_limit
input_data,
)
.sign_and_submit_then_watch(&self.signer)
.sign_and_submit_then_watch_default(&self.signer)
.await?;
log::info!("Call result: {:?}", result);
+10 -13
View File
@@ -32,10 +32,7 @@ use sp_core::{
Pair,
};
use sp_keyring::AccountKeyring;
use subxt::{
Error,
Signer,
};
use subxt::Error;
/// Helper function to generate a crypto pair from seed
fn get_from_seed(seed: &str) -> sr25519::Pair {
@@ -58,7 +55,7 @@ async fn validate_with_controller_account() {
.tx()
.staking()
.validate(default_validator_prefs())
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -75,7 +72,7 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
.tx()
.staking()
.validate(default_validator_prefs())
.sign_and_submit_then_watch(&alice_stash)
.sign_and_submit_then_watch_default(&alice_stash)
.await?
.wait_for_finalized_success()
.await;
@@ -96,7 +93,7 @@ async fn nominate_with_controller_account() {
.tx()
.staking()
.nominate(vec![bob.account_id().clone().into()])
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await
.unwrap()
.wait_for_finalized_success()
@@ -115,7 +112,7 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error<DispatchE
.tx()
.staking()
.nominate(vec![bob.account_id().clone().into()])
.sign_and_submit_then_watch(&alice_stash)
.sign_and_submit_then_watch_default(&alice_stash)
.await?
.wait_for_finalized_success()
.await;
@@ -139,7 +136,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
.tx()
.staking()
.nominate(vec![bob_stash.account_id().clone().into()])
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await?;
@@ -158,7 +155,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
.tx()
.staking()
.chill()
.sign_and_submit_then_watch(&alice_stash)
.sign_and_submit_then_watch_default(&alice_stash)
.await?
.wait_for_finalized_success()
.await;
@@ -173,7 +170,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error<DispatchError>> {
.tx()
.staking()
.chill()
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await?
@@ -197,7 +194,7 @@ async fn tx_bond() -> Result<(), Error<DispatchError>> {
100_000_000_000_000,
RewardDestination::Stash,
)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await;
@@ -213,7 +210,7 @@ async fn tx_bond() -> Result<(), Error<DispatchError>> {
100_000_000_000_000,
RewardDestination::Stash,
)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await;
+2 -2
View File
@@ -44,7 +44,7 @@ async fn test_sudo() -> Result<(), subxt::Error<DispatchError>> {
.tx()
.sudo()
.sudo(call)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await?
@@ -70,7 +70,7 @@ async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error<DispatchError>>
.tx()
.sudo()
.sudo_unchecked_weight(call, 0)
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await?
+1 -2
View File
@@ -24,7 +24,6 @@ use crate::{
};
use assert_matches::assert_matches;
use sp_keyring::AccountKeyring;
use subxt::Signer;
#[async_std::test]
async fn storage_account() -> Result<(), subxt::Error<DispatchError>> {
@@ -52,7 +51,7 @@ async fn tx_remark_with_event() -> Result<(), subxt::Error<DispatchError>> {
.tx()
.system()
.remark_with_event(b"remarkable".to_vec())
.sign_and_submit_then_watch(&alice)
.sign_and_submit_then_watch_default(&alice)
.await?
.wait_for_finalized_success()
.await?
+3 -3
View File
@@ -49,7 +49,7 @@ async fn storage_map_lookup() -> Result<(), subxt::Error<DispatchError>> {
.tx()
.system()
.remark(vec![1, 2, 3, 4, 5])
.sign_and_submit_then_watch(&signer)
.sign_and_submit_then_watch_default(&signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -114,7 +114,7 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error<DispatchError
.tx()
.assets()
.create(99, alice.clone().into(), 1)
.sign_and_submit_then_watch(&signer)
.sign_and_submit_then_watch_default(&signer)
.await?
.wait_for_finalized_success()
.await?;
@@ -122,7 +122,7 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error<DispatchError
.tx()
.assets()
.approve_transfer(99, bob.clone().into(), 123)
.sign_and_submit_then_watch(&signer)
.sign_and_submit_then_watch_default(&signer)
.await?
.wait_for_finalized_success()
.await?;
+4 -8
View File
@@ -22,18 +22,16 @@ pub(crate) use crate::{
use sp_core::sr25519::Pair;
use sp_keyring::AccountKeyring;
use subxt::{
extrinsic::ChargeAssetTxPayment,
Client,
DefaultConfig,
DefaultExtraWithTxPayment,
PairSigner,
SubstrateExtrinsicParams,
};
/// substrate node should be installed on the $PATH
const SUBSTRATE_NODE_PATH: &str = "substrate";
pub type NodeRuntimeSignedExtra =
DefaultExtraWithTxPayment<DefaultConfig, ChargeAssetTxPayment<DefaultConfig>>;
pub type NodeRuntimeParams = SubstrateExtrinsicParams<DefaultConfig>;
pub async fn test_node_process_with(
key: AccountKeyring,
@@ -60,7 +58,7 @@ pub async fn test_node_process() -> TestNodeProcess<DefaultConfig> {
pub struct TestContext {
pub node_proc: TestNodeProcess<DefaultConfig>,
pub api: node_runtime::RuntimeApi<DefaultConfig, NodeRuntimeSignedExtra>,
pub api: node_runtime::RuntimeApi<DefaultConfig, NodeRuntimeParams>,
}
impl TestContext {
@@ -76,8 +74,6 @@ pub async fn test_context() -> TestContext {
TestContext { node_proc, api }
}
pub fn pair_signer(
pair: Pair,
) -> PairSigner<DefaultConfig, NodeRuntimeSignedExtra, Pair> {
pub fn pair_signer(pair: Pair) -> PairSigner<DefaultConfig, Pair> {
PairSigner::new(pair)
}