Make sp_core and sp_runtime dependencies optional, and bump to latest (#760)

* begin porting over traits; remove Config use of Hash

* port over the Header bits that we need

* sp_core_hashing where possible, move Verify to PairSigner, remove unused errors

* tidy up Config things and move related bits into one place

* fix codegen

* copy Era over

* move AccountId, Address, Signer to Signer trait and a pass over fixing examples

* impl MultiAddress, MultiSignature, AccountId32 and add back to Config (for decoding later)

* Copy over StorageKey, StorageData, StorageChangeSet

* subxt core compiling with no sp_core or sp_runtime

* Get examples compiling

* pass over fixing tests

* cargo fmt

* clippy tweaks and update polkadot.rs

* fix codegen docs

* port over special DigestItem encoding/decoding

* clippy and doc fixes

* cargo fmt and example fix

* more cargo fmt-ing...

* substrate-extra to substrate-compat

* cargo.toml comments

* simplify PairSigner trait bounds

* move RPC types to a separate file

* fix docs

* Add some tests for things and other PR feedback

* bump to latest sp deps

* avoid needing substrate-compat feature in a test
This commit is contained in:
James Wilson
2023-01-10 12:02:41 +00:00
committed by GitHub
parent ea5daa444f
commit b316301d61
47 changed files with 2658 additions and 1736 deletions
+10 -30
View File
@@ -2,45 +2,25 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! Create signed or unsigned extrinsics.
//!
//! This modules exposes the extrinsic's parameters and the ability to sign an extrinsic.
//!
//! Create and submit extrinsics.
//!
//! An extrinsic is submitted with an "signed extra" and "additional" parameters, which can be
//! different for each chain. The trait [ExtrinsicParams] determines exactly which
//! additional and signed extra parameters are used when constructing an extrinsic.
//!
//!
//! The structure [BaseExtrinsicParams] is a base implementation of the trait which
//! configures most of the "signed extra" and "additional" parameters as needed for
//! Polkadot and Substrate nodes. Only the shape of the tip payments differs, leading to
//! [SubstrateExtrinsicParams] and [PolkadotExtrinsicParams] structs which pick an
//! appropriate shape for Substrate/Polkadot chains respectively.
//! different for each chain. The trait [`crate::config::ExtrinsicParams`] determines exactly which
//! additional and signed extra parameters are used when constructing an extrinsic, and is a part
//! of the chain configuration (see [`crate::config::Config`]).
mod params;
mod signer;
mod tx_client;
mod tx_payload;
mod tx_progress;
// The PairSigner impl currently relies on Substrate bits and pieces, so make it an optional
// feature if we want to avoid needing sp_core and sp_runtime.
#[cfg(feature = "substrate-compat")]
pub use self::signer::PairSigner;
pub use self::{
params::{
AssetTip,
BaseExtrinsicParams,
BaseExtrinsicParamsBuilder,
Era,
ExtrinsicParams,
PlainTip,
PolkadotExtrinsicParams,
PolkadotExtrinsicParamsBuilder,
SubstrateExtrinsicParams,
SubstrateExtrinsicParamsBuilder,
},
signer::{
PairSigner,
Signer,
},
signer::Signer,
tx_client::{
SubmittableExtrinsic,
TxClient,
-237
View File
@@ -1,237 +0,0 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{
utils::Encoded,
Config,
};
use codec::{
Compact,
Encode,
};
use core::fmt::Debug;
use derivative::Derivative;
// 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<Index, Hash>: Debug + 'static {
/// 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: Index,
genesis_hash: 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.
#[derive(Derivative)]
#[derivative(Debug(bound = "Tip: Debug"))]
pub struct BaseExtrinsicParams<T: Config, Tip: Debug> {
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.
#[derive(Derivative)]
#[derivative(
Debug(bound = "Tip: Debug"),
Clone(bound = "Tip: Clone"),
Copy(bound = "Tip: Copy"),
PartialEq(bound = "Tip: PartialEq")
)]
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: Debug + Encode + 'static> ExtrinsicParams<T::Index, T::Hash>
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, Debug, 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, Debug, 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)
}
}
+69 -50
View File
@@ -6,11 +6,6 @@
//! [substrate](https://github.com/paritytech/substrate) node via RPC.
use crate::Config;
use sp_core::Pair;
use sp_runtime::traits::{
IdentifyAccount,
Verify,
};
/// Signing transactions requires a [`Signer`]. This is responsible for
/// providing the "from" account that the transaction is being signed by,
@@ -29,55 +24,79 @@ pub trait Signer<T: Config> {
fn sign(&self, signer_payload: &[u8]) -> T::Signature;
}
/// A [`Signer`] implementation that can be constructed from an [`Pair`].
#[derive(Clone, Debug)]
pub struct PairSigner<T: Config, P: Pair> {
account_id: T::AccountId,
signer: P,
}
#[cfg(feature = "substrate-compat")]
pub use pair_signer::PairSigner;
impl<T, P> PairSigner<T, P>
where
T: Config,
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`].
pub fn new(signer: P) -> Self {
let account_id =
<T::Signature as Verify>::Signer::from(signer.public()).into_account();
Self { account_id, signer }
// A signer suitable for substrate based chains. This provides compatibility with Substrate
// packages like sp_keyring and such, and so relies on sp_core and sp_runtime to be included.
#[cfg(feature = "substrate-compat")]
mod pair_signer {
use super::Signer;
use crate::Config;
use sp_core::Pair as PairT;
use sp_runtime::{
traits::{
IdentifyAccount,
Verify,
},
AccountId32 as SpAccountId32,
MultiSignature as SpMultiSignature,
};
/// A [`Signer`] implementation that can be constructed from an [`sp_core::Pair`].
#[derive(Clone, Debug)]
pub struct PairSigner<T: Config, Pair> {
account_id: T::AccountId,
signer: Pair,
}
/// Returns the [`Pair`] implementation used to construct this.
pub fn signer(&self) -> &P {
&self.signer
impl<T, Pair> PairSigner<T, Pair>
where
T: Config,
Pair: PairT,
// We go via an sp_runtime::MultiSignature. We can probably generalise this
// by implementing some of these traits on our built-in MultiSignature and then
// requiring them on all T::Signatures, to avoid any go-between.
<SpMultiSignature as Verify>::Signer: From<Pair::Public>,
T::AccountId: From<SpAccountId32>,
{
/// Creates a new [`Signer`] from an [`sp_core::Pair`].
pub fn new(signer: Pair) -> Self {
let account_id = <SpMultiSignature as Verify>::Signer::from(signer.public())
.into_account();
Self {
account_id: account_id.into(),
signer,
}
}
/// Returns the [`sp_core::Pair`] implementation used to construct this.
pub fn signer(&self) -> &Pair {
&self.signer
}
/// Return the account ID.
pub fn account_id(&self) -> &T::AccountId {
&self.account_id
}
}
/// Return the account ID.
pub fn account_id(&self) -> &T::AccountId {
&self.account_id
}
}
impl<T, P> Signer<T> for PairSigner<T, P>
where
T: Config,
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 address(&self) -> T::Address {
self.account_id.clone().into()
}
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.signer.sign(signer_payload).into()
impl<T, Pair> Signer<T> for PairSigner<T, Pair>
where
T: Config,
Pair: PairT,
Pair::Signature: Into<T::Signature>,
{
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()
}
}
}
+31 -26
View File
@@ -8,27 +8,29 @@ use crate::{
OfflineClientT,
OnlineClientT,
},
config::{
Config,
ExtrinsicParams,
Hasher,
},
error::Error,
tx::{
ExtrinsicParams,
Signer,
Signer as SignerT,
TxProgress,
},
utils::{
Encoded,
PhantomDataSendSync,
},
Config,
};
use codec::{
Compact,
Encode,
};
use derivative::Derivative;
use sp_runtime::{
traits::Hash,
ApplyExtrinsicResult,
};
// This is returned from an API below, so expose it here.
pub use crate::rpc::types::DryRunResult;
/// A client for working with transactions.
#[derive(Derivative)]
@@ -121,15 +123,16 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
}
/// Creates a raw signed extrinsic without submitting it.
pub fn create_signed_with_nonce<Call>(
pub fn create_signed_with_nonce<Call, Signer>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
signer: &Signer,
account_nonce: T::Index,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Signer: SignerT<T>,
{
// 1. Validate this call against the current node metadata if the call comes
// with a hash allowing us to do so.
@@ -166,7 +169,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
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))
signer.sign(T::Hasher::hash_of(&bytes).as_ref())
} else {
signer.sign(&bytes)
}
@@ -214,14 +217,15 @@ where
C: OnlineClientT<T>,
{
/// Creates a raw signed extrinsic, without submitting it.
pub async fn create_signed<Call>(
pub async fn create_signed<Call, Signer>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
signer: &Signer,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
Signer: SignerT<T>,
{
// Get nonce from the node.
let account_nonce = self
@@ -238,13 +242,14 @@ where
///
/// Returns a [`TxProgress`], 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<Call>(
pub async fn sign_and_submit_then_watch_default<Call, Signer>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
signer: &Signer,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
{
self.sign_and_submit_then_watch(call, signer, Default::default())
@@ -255,14 +260,15 @@ where
///
/// Returns a [`TxProgress`], 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<Call>(
pub async fn sign_and_submit_then_watch<Call, Signer>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
signer: &Signer,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
Signer: SignerT<T>,
{
self.create_signed(call, signer, other_params)
.await?
@@ -280,13 +286,14 @@ where
///
/// 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<Call>(
pub async fn sign_and_submit_default<Call, Signer>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
signer: &Signer,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
{
self.sign_and_submit(call, signer, Default::default()).await
@@ -300,14 +307,15 @@ where
///
/// 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<Call>(
pub async fn sign_and_submit<Call, Signer>(
&self,
call: &Call,
signer: &(dyn Signer<T> + Send + Sync),
signer: &Signer,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Index, T::Hash>>::OtherParams,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
Signer: SignerT<T>,
{
self.create_signed(call, signer, other_params)
.await?
@@ -366,7 +374,7 @@ where
/// and obtain details about it, once it has made it into a block.
pub async fn submit_and_watch(&self) -> Result<TxProgress<T, C>, Error> {
// Get a hash of the extrinsic (we'll need this later).
let ext_hash = T::Hashing::hash_of(&self.encoded);
let ext_hash = T::Hasher::hash_of(&self.encoded);
// Submit and watch for transaction progress.
let sub = self.client.rpc().watch_extrinsic(&self.encoded).await?;
@@ -388,11 +396,8 @@ where
/// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
///
/// Returns `Ok` with an [`ApplyExtrinsicResult`], which is the result of applying of an extrinsic.
pub async fn dry_run(
&self,
at: Option<T::Hash>,
) -> Result<ApplyExtrinsicResult, Error> {
/// Returns `Ok` with a [`DryRunResult`], which is the result of attempting to dry run the extrinsic.
pub async fn dry_run(&self, at: Option<T::Hash>) -> Result<DryRunResult, Error> {
self.client.rpc().dry_run(self.encoded(), at).await
}
}
+3 -5
View File
@@ -15,7 +15,7 @@ use crate::{
TransactionError,
},
events::EventsClient,
rpc::{
rpc::types::{
Subscription,
SubstrateTxStatus,
},
@@ -26,9 +26,6 @@ use futures::{
Stream,
StreamExt,
};
use sp_runtime::traits::Hash;
pub use sp_runtime::traits::SignedExtension;
/// This struct represents a subscription to the progress of some transaction.
#[derive(Derivative)]
@@ -373,7 +370,8 @@ impl<T: Config, C: OnlineClientT<T>> TxInBlock<T, C> {
let extrinsic_idx = block.block.extrinsics
.iter()
.position(|ext| {
let hash = T::Hashing::hash_of(&ext.0);
use crate::config::Hasher;
let hash = T::Hasher::hash_of(&ext.0);
hash == self.ext_hash
})
// If we successfully obtain the block hash we think contains our