Add subxt_signer crate for native & WASM compatible signing (#1016)

* Add and use subxt-signer crate for WASM compatible signing

* cargo fmt

* dev keypairs already references

* WIP fix various breakages

* re-jig features to be simpler and various test fixes etc

* doc and web fix

* fix various bits and pieces

* fix a test I broke

* dev-deps can't be linked to in docs, hrmph

* cargo fmt

* another doc link

* document the subxt_signer crate more thoroughly

* move feature flag for consistency

* more docs, no default subxt feature flag on signer, update release instrs

* Add missing license header

* unwrap_inner => into_inner

* extend a test a little to better check derive junctions

* note more clearly that the crypto bits come from sp_core::crypto
This commit is contained in:
James Wilson
2023-06-20 11:32:12 +01:00
committed by GitHub
parent d091c091ae
commit b4eb406ee5
52 changed files with 1388 additions and 302 deletions
-4
View File
@@ -16,8 +16,6 @@
//! We can use the statically generated interface to build constant queries:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
//! pub mod polkadot {}
//!
@@ -27,10 +25,8 @@
//! Alternately, we can dynamically construct a constant query:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt::dynamic::Value;
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let storage_query = subxt::dynamic::constant("System", "BlockLength");
//! ```
//!
-4
View File
@@ -22,8 +22,6 @@
//! We can use the statically generated interface to build runtime calls:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
//! pub mod polkadot {}
//!
@@ -33,10 +31,8 @@
//! Alternately, we can dynamically construct a runtime call:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt::dynamic::Value;
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let runtime_call = subxt::dynamic::runtime_api_call(
//! "Metadata",
//! "metadata_versions",
+4 -6
View File
@@ -16,12 +16,12 @@
//! We can use the statically generated interface to build storage queries:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt_signer::sr25519::dev;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
//! pub mod polkadot {}
//!
//! let account = AccountKeyring::Alice.to_account_id().into();
//! let account = dev::alice().public_key().into();
//! let storage_query = polkadot::storage().system().account(&account);
//! ```
//!
@@ -29,10 +29,10 @@
//! validated until it's submitted:
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//! use subxt_signer::sr25519::dev;
//! use subxt::dynamic::Value;
//!
//! let account = AccountKeyring::Alice.to_account_id();
//! let account = dev::alice().public_key();
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
//! Value::from_bytes(account)
//! ]);
@@ -43,8 +43,6 @@
//! will only be available on static constructors when iteration is actually possible):
//!
//! ```rust,no_run
//! use sp_keyring::AccountKeyring;
//!
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
//! pub mod polkadot {}
//!
+42 -17
View File
@@ -60,28 +60,54 @@
//! ## Signing it
//!
//! You'll normally need to sign an extrinsic to prove that it originated from an account that you
//! control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt
//! who the extrinsic is from, and takes care of signing the relevant details to prove this.
//! control. To do this, you will typically first create a [`crate::tx::Signer`] instance, which tells
//! Subxt who the extrinsic is from, and takes care of signing the relevant details to prove this.
//!
//! Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the
//! `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that
//! to sign transactions:
//! There are two main ways to create a compatible signer instance:
//! 1. The `subxt_signer` crate provides a WASM compatible implementation of [`crate::tx::Signer`]
//! for chains which require sr25519 signatures (requires the `subxt` feature to be enabled).
//! 2. Alternately, Subxt can use instances of Substrate's [`sp_core::Pair`] to sign things by wrapping
//! them in a [`crate::tx::PairSigner`] (requires the `substrate-compat` feature to be enabled).
//!
//! Going for 1 leads to fewer dependencies being imported and WASM compatibility out of the box via
//! the `web` feature flag. Going for 2 is useful if you're already using the Substrate dependencies or
//! need additional signing algorithms that `subxt_signer` doesn't support, and don't care about WASM
//! compatibility.
//!
//! Let's see how to use each of these approaches:
//!
//! ```rust
//! use subxt::config::PolkadotConfig;
//! use std::str::FromStr;
//!
//! //// 1. Use a `subxt_signer` impl:
//! use subxt_signer::{ SecretUri, sr25519 };
//!
//! // Get hold of a `Signer` for a test account:
//! let alice = sr25519::dev::alice();
//!
//! // Or generate a keypair, here from an SURI:
//! let uri = SecretUri::from_str("vessel ladder alter error federal sibling chat ability sun glass valve picture/0/1///Password")
//! .expect("valid URI");
//! let keypair = sr25519::Keypair::from_uri(&uri)
//! .expect("valid keypair");
//!
//! //// 2. Use the corresponding `sp_core::Pair` impl:
//! use subxt::tx::PairSigner;
//! use sp_core::Pair;
//! use subxt::config::PolkadotConfig;
//!
//! // Get hold of a `Signer` given a test account:
//! let pair = sp_keyring::AccountKeyring::Alice.pair();
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
//! // Get hold of a `Signer` for a test account:
//! let alice = sp_keyring::AccountKeyring::Alice.pair();
//! let alice = PairSigner::<PolkadotConfig,_>::new(alice);
//!
//! // Or generate an `sr25519` keypair to use:
//! let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password"));
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
//! // Or generate a keypair, here from an SURI:
//! let keypair = sp_core::sr25519::Pair::from_string("vessel ladder alter error federal sibling chat ability sun glass valve picture/0/1///Password", None)
//! .expect("valid URI");
//! let keypair = PairSigner::<PolkadotConfig,_>::new(keypair);
//! ```
//!
//! See the [`sp_core::Pair`] docs for more ways to generate them.
//! See the `subxt_signer::sr25519::Keypair` or the [`sp_core::Pair`] docs for more ways to construct
//! and work with key pairs.
//!
//! If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use
//! custom signing logic, or you can use some external signing logic, like so:
@@ -118,10 +144,9 @@
//! let signature;
//! let address;
//! # use subxt::tx::Signer;
//! # let pair = sp_keyring::AccountKeyring::Alice.pair();
//! # let signer = subxt::tx::PairSigner::<PolkadotConfig,_>::new(pair);
//! # signature = signer.sign(&signer_payload);
//! # address = signer.address();
//! # let signer = subxt_signer::sr25519::dev::alice();
//! # signature = signer.sign(&signer_payload).into();
//! # address = signer.public_key().to_address();
//!
//! // Now we can build an tx, which one can call `submit` or `submit_and_watch`
//! // on to submit to a node and optionally watch the status.
+1 -4
View File
@@ -16,8 +16,5 @@ pub use online_client::{
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError,
};
#[cfg(any(
feature = "jsonrpsee-ws",
all(feature = "jsonrpsee-web", target_arch = "wasm32")
))]
#[cfg(feature = "jsonrpsee")]
pub use online_client::default_rpc_client;
+4 -10
View File
@@ -55,10 +55,7 @@ impl<T: Config> std::fmt::Debug for OnlineClient<T> {
}
/// The default RPC client that's used (based on [`jsonrpsee`]).
#[cfg(any(
feature = "jsonrpsee-ws",
all(feature = "jsonrpsee-web", target_arch = "wasm32")
))]
#[cfg(feature = "jsonrpsee")]
pub async fn default_rpc_client<U: AsRef<str>>(url: U) -> Result<impl RpcClientT, Error> {
let client = jsonrpsee_helpers::client(url.as_ref())
.await
@@ -67,10 +64,7 @@ pub async fn default_rpc_client<U: AsRef<str>>(url: U) -> Result<impl RpcClientT
}
// The default constructors assume Jsonrpsee.
#[cfg(any(
feature = "jsonrpsee-ws",
all(feature = "jsonrpsee-web", target_arch = "wasm32")
))]
#[cfg(feature = "jsonrpsee")]
impl<T: Config> OnlineClient<T> {
/// Construct a new [`OnlineClient`] using default settings which
/// point to a locally running node on `ws://127.0.0.1:9944`.
@@ -428,7 +422,7 @@ impl Update {
}
// helpers for a jsonrpsee specific OnlineClient.
#[cfg(feature = "jsonrpsee-ws")]
#[cfg(all(feature = "jsonrpsee", feature = "native"))]
mod jsonrpsee_helpers {
pub use jsonrpsee::{
client_transport::ws::{InvalidUri, Receiver, Sender, Uri, WsTransportClientBuilder},
@@ -458,7 +452,7 @@ mod jsonrpsee_helpers {
}
// helpers for a jsonrpsee specific OnlineClient.
#[cfg(all(feature = "jsonrpsee-web", target_arch = "wasm32"))]
#[cfg(all(feature = "jsonrpsee", feature = "web"))]
mod jsonrpsee_helpers {
pub use jsonrpsee::{
client_transport::web,
+13 -1
View File
@@ -19,7 +19,7 @@ use std::sync::Arc;
/// A collection of events obtained from a block, bundled with the necessary
/// information needed to decode and iterate over them.
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Clone(bound = ""))]
#[derivative(Clone(bound = ""))]
pub struct Events<T: Config> {
metadata: Metadata,
block_hash: T::Hash,
@@ -31,6 +31,18 @@ pub struct Events<T: Config> {
num_events: u32,
}
// Ignore the Metadata when debug-logging events; it's big and distracting.
impl<T: Config> std::fmt::Debug for Events<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Events")
.field("block_hash", &self.block_hash)
.field("event_bytes", &self.event_bytes)
.field("start_idx", &self.start_idx)
.field("num_events", &self.num_events)
.finish()
}
}
impl<T: Config> Events<T> {
pub(crate) fn new(metadata: Metadata, block_hash: T::Hash, event_bytes: Vec<u8>) -> Self {
// event_bytes is a SCALE encoded vector of events. So, pluck the
+15 -7
View File
@@ -33,23 +33,31 @@
)]
#![allow(clippy::type_complexity)]
#[cfg(any(
all(feature = "web", feature = "native"),
not(any(feature = "web", feature = "native"))
))]
compile_error!("subxt: exactly one of the 'web' and 'native' features should be used.");
#[cfg(all(feature = "web", feature = "substrate-compat"))]
compile_error!("subxt: the 'substrate-compat' feature is not compatible with the 'web' feature.");
// The guide is here.
pub mod book;
// Suppress an unused dependency warning because tokio is
// only used in example code snippets at the time of writing.
#[cfg(test)]
use tokio as _;
mod only_used_in_docs_or_tests {
use subxt_signer as _;
use tokio as _;
}
// Used to enable the js feature for wasm.
#[cfg(target_arch = "wasm32")]
#[cfg(feature = "web")]
#[allow(unused_imports)]
pub use getrandom as _;
#[cfg(all(feature = "jsonrpsee-ws", feature = "jsonrpsee-web"))]
std::compile_error!(
"Both the features `jsonrpsee-ws` and `jsonrpsee-web` are enabled which are mutually exclusive"
);
pub mod blocks;
pub mod client;
pub mod config;
+3 -3
View File
@@ -12,7 +12,7 @@ use crate::Config;
/// as well as actually signing a SCALE encoded payload.
pub trait Signer<T: Config> {
/// Return the "from" account ID.
fn account_id(&self) -> &T::AccountId;
fn account_id(&self) -> T::AccountId;
/// Return the "from" address.
fn address(&self) -> T::Address;
@@ -83,8 +83,8 @@ mod pair_signer {
Pair: PairT,
Pair::Signature: Into<T::Signature>,
{
fn account_id(&self) -> &T::AccountId {
&self.account_id
fn account_id(&self) -> T::AccountId {
self.account_id.clone()
}
fn address(&self) -> T::Address {
+1 -1
View File
@@ -211,7 +211,7 @@ where
Call: TxPayload,
Signer: SignerT<T>,
{
let account_nonce = self.account_nonce(signer.account_id()).await?;
let account_nonce = self.account_nonce(&signer.account_id()).await?;
self.create_signed_with_nonce(call, signer, account_nonce, other_params)
}