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
+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.