Make ExtrinsicParams more flexible, and introduce signed extensions (#1107)

* WIP new SignedExtensions

* Integrate new signex extension stuff, update docs, remove Static wrapper

* remove impl Into in tx_client to avoid type inference annoyances

* clippy and fix example

* Fix book links

* clippy

* book tweaks

* fmt: remove spaces

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* re-expose Era in utils, and tweak wasm-example

* clippy; remove useless conversion

---------

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
James Wilson
2023-08-08 09:37:06 +01:00
committed by GitHub
parent 9cfac4eec7
commit 9b86b55b56
20 changed files with 1106 additions and 624 deletions
+44 -44
View File
@@ -113,7 +113,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -537,7 +537,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -559,7 +559,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core 0.20.3",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -762,7 +762,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -1704,7 +1704,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -2005,9 +2005,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "scale-bits"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd7aca73785181cc41f0bbe017263e682b585ca660540ba569133901d013ecf"
checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89"
dependencies = [
"parity-scale-codec",
"scale-info",
@@ -2016,24 +2016,24 @@ dependencies = [
[[package]]
name = "scale-decode"
version = "0.7.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0459d00b0dbd2e765009924a78ef36b2ff7ba116292d732f00eb0ed8e465d15"
checksum = "7789f5728e4e954aaa20cadcc370b99096fb8645fca3c9333ace44bb18f30095"
dependencies = [
"derive_more",
"parity-scale-codec",
"primitive-types",
"scale-bits",
"scale-decode-derive",
"scale-info",
"smallvec",
"thiserror",
]
[[package]]
name = "scale-decode-derive"
version = "0.7.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4391f0dfbb6690f035f6d2a15d6a12f88cc5395c36bcc056db07ffa2a90870ec"
checksum = "27873eb6005868f8cc72dcfe109fae664cf51223d35387bc2f28be4c28d94c47"
dependencies = [
"darling 0.14.4",
"proc-macro-crate",
@@ -2044,24 +2044,24 @@ dependencies = [
[[package]]
name = "scale-encode"
version = "0.3.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0401b7cdae8b8aa33725f3611a051358d5b32887ecaa0fda5953a775b2d4d76"
checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5"
dependencies = [
"derive_more",
"parity-scale-codec",
"primitive-types",
"scale-bits",
"scale-encode-derive",
"scale-info",
"smallvec",
"thiserror",
]
[[package]]
name = "scale-encode-derive"
version = "0.3.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "316e0fb10ec0fee266822bd641bab5e332a4ab80ef8c5b5ff35e5401a394f5a6"
checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25"
dependencies = [
"darling 0.14.4",
"proc-macro-crate",
@@ -2098,12 +2098,13 @@ dependencies = [
[[package]]
name = "scale-value"
version = "0.10.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2096d36e94ce9bf87d8addb752423b6b19730dc88edd7cc452bb2b90573f7a7"
checksum = "6538d1cc1af9c0baf401c57da8a6d4730ef582db0d330d2efa56ec946b5b0283"
dependencies = [
"base58",
"blake2",
"derive_more",
"either",
"frame-metadata 15.1.0",
"parity-scale-codec",
@@ -2112,7 +2113,6 @@ dependencies = [
"scale-encode",
"scale-info",
"serde",
"thiserror",
"yap",
]
@@ -2194,9 +2194,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "serde"
version = "1.0.175"
version = "1.0.181"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890"
dependencies = [
"serde_derive",
]
@@ -2214,20 +2214,20 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.175"
version = "1.0.181"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
name = "serde_json"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
dependencies = [
"itoa",
"ryu",
@@ -2490,7 +2490,7 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142"
[[package]]
name = "subxt"
version = "0.30.0"
version = "0.31.0"
dependencies = [
"base58",
"blake2",
@@ -2521,7 +2521,7 @@ dependencies = [
[[package]]
name = "subxt-codegen"
version = "0.30.0"
version = "0.31.0"
dependencies = [
"frame-metadata 16.0.0",
"heck",
@@ -2532,14 +2532,14 @@ dependencies = [
"quote",
"scale-info",
"subxt-metadata",
"syn 2.0.27",
"syn 2.0.28",
"thiserror",
"tokio",
]
[[package]]
name = "subxt-lightclient"
version = "0.30.0"
version = "0.31.0"
dependencies = [
"futures",
"futures-timer",
@@ -2563,17 +2563,17 @@ dependencies = [
[[package]]
name = "subxt-macro"
version = "0.30.0"
version = "0.31.0"
dependencies = [
"darling 0.20.3",
"proc-macro-error",
"subxt-codegen",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
name = "subxt-metadata"
version = "0.30.0"
version = "0.31.0"
dependencies = [
"frame-metadata 16.0.0",
"parity-scale-codec",
@@ -2595,9 +2595,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.27"
version = "2.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
dependencies = [
"proc-macro2",
"quote",
@@ -2647,7 +2647,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -2700,7 +2700,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -2783,7 +2783,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
[[package]]
@@ -2895,7 +2895,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
"wasm-bindgen-shared",
]
@@ -2929,7 +2929,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3136,9 +3136,9 @@ dependencies = [
[[package]]
name = "yap"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2a7eb6d82a11e4d0b8e6bda8347169aff4ccd8235d039bba7c47482d977dcf7"
checksum = "ff4524214bc4629eba08d78ceb1d6507070cc0bcbbed23af74e19e6e924a24cf"
[[package]]
name = "yew"
@@ -3226,5 +3226,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.27",
"syn 2.0.28",
]
+16 -11
View File
@@ -8,7 +8,7 @@ use subxt::tx::SubmittableExtrinsic;
use subxt::tx::TxPayload;
use subxt::utils::{AccountId32, MultiSignature};
use crate::services::{extension_signature_for_partial_extrinsic, get_accounts, polkadot, Account};
use crate::services::{extension_signature_for_extrinsic, get_accounts, polkadot, Account};
use web_sys::HtmlInputElement;
use yew::prelude::*;
@@ -139,15 +139,15 @@ impl Component for SigningExamplesComponent {
ctx.link()
.send_future(
async move {
let partial_extrinsic =
match api.tx().create_partial_signed(&remark_call, &account_id, Default::default()).await {
Ok(partial_extrinsic) => partial_extrinsic,
Err(err) => {
return Message::Error(anyhow!("could not create partial extrinsic:\n{:?}", err));
}
};
let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else {
return Message::Error(anyhow!("Fetching account nonce failed"));
};
let Ok(signature) = extension_signature_for_partial_extrinsic(&partial_extrinsic, &api, &account_id, account_source, account_address).await else {
let Ok(call_data) = api.tx().call_data(&remark_call) else {
return Message::Error(anyhow!("could not encode call data"));
};
let Ok(signature) = extension_signature_for_extrinsic(&call_data, &api, account_nonce, account_source, account_address).await else {
return Message::Error(anyhow!("Signing via extension failed"));
};
@@ -155,7 +155,12 @@ impl Component for SigningExamplesComponent {
return Message::Error(anyhow!("MultiSignature Decoding"));
};
let signed_extrinsic = partial_extrinsic.sign_with_address_and_signature(&account_id.into(), &multi_signature);
let Ok(partial_signed) = api.tx().create_partial_signed_with_nonce(&remark_call, account_nonce, Default::default()) else {
return Message::Error(anyhow!("PartialExtrinsic creation failed"));
};
// Apply the signature
let signed_extrinsic = partial_signed.sign_with_address_and_signature(&account_id.into(), &multi_signature);
// do a dry run (to debug in the js console if the extrinsic would work)
let dry_res = signed_extrinsic.dry_run(None).await;
@@ -193,7 +198,7 @@ impl Component for SigningExamplesComponent {
match submit_wait_finalized_and_get_extrinsic_success_event(
signed_extrinsic,
)
.await
.await
{
Ok(remark_event) => Message::ExtrinsicFinalized { remark_event },
Err(err) => Message::ExtrinsicFailed(err),
+16 -25
View File
@@ -4,10 +4,8 @@ use js_sys::Promise;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::fmt::Write;
use subxt::ext::codec::Encode;
use subxt::tx::PartialExtrinsic;
use subxt::ext::codec::{Compact, Encode};
use subxt::{self, OnlineClient, PolkadotConfig};
use subxt::utils::AccountId32;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use yew::{AttrValue, Callback};
@@ -117,37 +115,30 @@ fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
fn encode_to_hex<E: Encode>(input: &E) -> String {
fn encode_then_hex<E: Encode>(input: &E) -> String {
format!("0x{}", hex::encode(input.encode()))
}
/// this is used because numeric types (e.g. u32) are encoded as little-endian via scale (e.g. 9430 -> d6240000)
/// while we need a big-endian representation for the json (e.g. 9430 -> 000024d6).
fn encode_to_hex_reverse<E: Encode>(input: &E) -> String {
let mut bytes = input.encode();
bytes.reverse();
format!("0x{}", hex::encode(bytes))
}
/// communicates with JavaScript to obtain a signature for the `partial_extrinsic` via a browser extension (e.g. polkadot-js or Talisman)
///
/// Some parameters are hard-coded here and not taken from the partial_extrinsic itself (mortality_checkpoint, era, tip).
pub async fn extension_signature_for_partial_extrinsic(
partial_extrinsic: &PartialExtrinsic<PolkadotConfig, OnlineClient<PolkadotConfig>>,
pub async fn extension_signature_for_extrinsic(
call_data: &[u8],
api: &OnlineClient<PolkadotConfig>,
account_id: &AccountId32,
account_nonce: u64,
account_source: String,
account_address: String,
) -> Result<Vec<u8>, anyhow::Error> {
let spec_version = encode_to_hex_reverse(&api.runtime_version().spec_version);
let transaction_version = encode_to_hex_reverse(&api.runtime_version().transaction_version);
let mortality_checkpoint = encode_to_hex(&api.genesis_hash());
let era = encode_to_hex(&subxt::config::extrinsic_params::Era::Immortal);
let genesis_hash = encode_to_hex(&api.genesis_hash());
let method = to_hex(partial_extrinsic.call_data());
let nonce = api.tx().account_nonce(account_id).await?;
let nonce = encode_to_hex_reverse(&nonce);
let genesis_hash = encode_then_hex(&api.genesis_hash());
// These numbers aren't SCALE encoded; their bytes are just converted to hex:
let spec_version = to_hex(&api.runtime_version().spec_version.to_be_bytes());
let transaction_version = to_hex(&api.runtime_version().transaction_version.to_be_bytes());
let nonce = to_hex(&account_nonce.to_be_bytes());
// If you construct a mortal transaction, then this block hash needs to correspond
// to the block number passed to `Era::mortal()`.
let mortality_checkpoint = encode_then_hex(&api.genesis_hash());
let era = encode_then_hex(&subxt::utils::Era::Immortal);
let method = to_hex(call_data);
let signed_extensions: Vec<String> = api
.metadata()
.extrinsic()
@@ -155,7 +146,7 @@ pub async fn extension_signature_for_partial_extrinsic(
.iter()
.map(|e| e.identifier().to_string())
.collect();
let tip = encode_to_hex(&subxt::config::polkadot::PlainTip::new(0));
let tip = encode_then_hex(&Compact(0u128));
let payload = json!({
"specVersion": spec_version,
@@ -1,28 +0,0 @@
use subxt::{
config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig},
OnlineClient,
};
/// Define a custom config type (see the `subxt::config::Config` docs for
/// more information about each type):
enum MyConfig {}
impl Config for MyConfig {
// We can point to the default types if we don't need to change things:
type Hash = <SubstrateConfig as Config>::Hash;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <SubstrateConfig as Config>::Address;
type Signature = <SubstrateConfig as Config>::Signature;
// ExtrinsicParams makes use of the index type, so we need to tweak it
// too to align with our modified index type, above:
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client which uses the custom config:
let _api = OnlineClient::<MyConfig>::new().await?;
Ok(())
}
+66 -65
View File
@@ -1,95 +1,96 @@
use codec::Encode;
use primitive_types::H256;
use subxt::config::{Config, ExtrinsicParams};
use subxt::client::OfflineClientT;
use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod runtime {}
// We don't need to construct this at runtime,
// so an empty enum is appropriate:
pub enum StatemintConfig {}
pub enum CustomConfig {}
impl Config for StatemintConfig {
impl Config for CustomConfig {
type Hash = subxt::utils::H256;
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = StatemintExtrinsicParams;
type ExtrinsicParams = CustomExtrinsicParams<Self>;
}
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct StatemintExtrinsicParams {
extra_params: StatemintExtraParams,
additional_params: StatemintAdditionalParams,
// This represents some arbitrary (and nonsensical) custom parameters that
// will be attached to transaction extra and additional payloads:
pub struct CustomExtrinsicParams<T: Config> {
genesis_hash: T::Hash,
tip: u128,
foo: bool,
}
impl ExtrinsicParams<H256> for StatemintExtrinsicParams {
// We need these additional values that aren't otherwise
// provided. Calls like api.tx().sign_and_submit_then_watch()
// allow the user to provide an instance of these, so it's wise
// to give this a nicer interface in reality:
type OtherParams = (
sp_core::H256,
sp_runtime::generic::Era,
ChargeAssetTxPayment,
);
// We can provide a "pretty" interface to allow users to provide these:
#[derive(Default)]
pub struct CustomExtrinsicParamsBuilder {
tip: u128,
foo: bool,
}
impl CustomExtrinsicParamsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn tip(mut self, value: u128) -> Self {
self.tip = value;
self
}
pub fn enable_foo(mut self) -> Self {
self.foo = true;
self
}
}
// Describe how to fetch and then encode the params:
impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
type OtherParams = CustomExtrinsicParamsBuilder;
type Error = std::convert::Infallible;
// Gather together all of the params we will need to encode:
fn new(
spec_version: u32,
tx_version: u32,
nonce: u64,
genesis_hash: H256,
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Self {
let (mortality_hash, era, charge) = other_params;
let extra_params = StatemintExtraParams { era, nonce, charge };
let additional_params = StatemintAdditionalParams {
spec_version,
tx_version,
genesis_hash,
mortality_hash,
};
Self {
extra_params,
additional_params,
}
) -> Result<Self, Self::Error> {
Ok(Self {
genesis_hash: client.genesis_hash(),
tip: other_params.tip,
foo: other_params.foo,
})
}
}
// Encode the relevant params when asked:
// Encode the relevant params when asked:
impl<T: Config> ExtrinsicParamsEncoder for CustomExtrinsicParams<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.extra_params.encode_to(v);
(self.tip, self.foo).encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.additional_params.encode_to(v);
self.genesis_hash.encode_to(v)
}
}
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct StatemintExtraParams {
era: sp_runtime::generic::Era,
nonce: u64,
charge: ChargeAssetTxPayment,
}
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct ChargeAssetTxPayment {
#[codec(compact)]
tip: u128,
asset_id: Option<u32>,
}
#[derive(Encode, Debug, Clone, Eq, PartialEq)]
pub struct StatemintAdditionalParams {
spec_version: u32,
tx_version: u32,
genesis_hash: sp_core::H256,
mortality_hash: sp_core::H256,
}
#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let _client_fut = subxt::OnlineClient::<StatemintConfig>::new();
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Build your custom "OtherParams":
let tx_config = CustomExtrinsicParamsBuilder::new().tip(1234).enable_foo();
// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config)
.await;
}
@@ -0,0 +1,101 @@
use codec::Encode;
use subxt::client::OfflineClientT;
use subxt::config::signed_extensions;
use subxt::config::{
Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod runtime {}
// We don't need to construct this at runtime,
// so an empty enum is appropriate:
pub enum CustomConfig {}
impl Config for CustomConfig {
type Hash = subxt::utils::H256;
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = signed_extensions::AnyOf<
Self,
(
// Load in the existing signed extensions we're interested in
// (if the extension isn't actually needed it'll just be ignored):
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<Self>,
signed_extensions::CheckMortality<Self>,
signed_extensions::ChargeAssetTxPayment,
signed_extensions::ChargeTransactionPayment,
// And add a new one of our own:
CustomSignedExtension,
),
>;
}
// Our custom signed extension doesn't do much:
pub struct CustomSignedExtension;
// Give the extension a name; this allows `AnyOf` to look it
// up in the chain metadata in order to know when and if to use it.
impl<T: Config> signed_extensions::SignedExtension<T> for CustomSignedExtension {
const NAME: &'static str = "CustomSignedExtension";
}
// Gather together any params we need for our signed extension, here none.
impl<T: Config> ExtrinsicParams<T> for CustomSignedExtension {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CustomSignedExtension)
}
}
// Encode whatever the extension needs to provide when asked:
impl ExtrinsicParamsEncoder for CustomSignedExtension {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
"Hello".encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
true.encode_to(v)
}
}
// When composing a tuple of signed extensions, the user parameters we need must
// be able to convert `Into` a tuple of corresponding `OtherParams`. Here, we just
// "hijack" the default param builder, but add the `OtherParams` (`()`) for our
// new signed extension at the end, to make the types line up. IN reality you may wish
// to construct an entirely new interface to provide the relevant `OtherParams`.
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::OtherParams {
let (a, b, c, d, e, f, g) = params.build();
(a, b, c, d, e, f, g, ())
}
#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Configure the tx params:
let tx_config = DefaultExtrinsicParamsBuilder::new().tip(1234);
// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config))
.await;
}
+8 -4
View File
@@ -1,4 +1,4 @@
use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params};
use subxt::config::polkadot::PolkadotExtrinsicParamsBuilder as Params;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
@@ -14,10 +14,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let dest = dev::bob().public_key().into();
let tx = polkadot::tx().balances().transfer(dest, 10_000);
// Configure the transaction parameters; for Polkadot the tip and era:
let latest_block = api.blocks().at_latest().await?;
// Configure the transaction parameters; we give a small tip and set the
// transaction to live for 32 blocks from the `latest_block` above.
let tx_params = Params::new()
.tip(PlainTip::new(1_000))
.era(Era::Immortal, api.genesis_hash());
.tip(1_000)
.mortal(latest_block.header(), 32)
.build();
// submit the transaction:
let from = dev::alice();
+16 -23
View File
@@ -2,25 +2,21 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! # Configuring the Subxt client
//! # The Subxt client.
//!
//! Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online
//! client](crate::client::OnlineClient). These are backed by the traits
//! [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's
//! possible for users to implement their own clients, although this isn't generally expected.
//! The client forms the entry point to all of the Subxt APIs. Every client implements one or
//! both of [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`].
//!
//! Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give
//! the client certain information about how to interact with a node that isn't otherwise available
//! or possible to include in the node metadata.
//! Subxt ships with three clients which implement one or both of traits:
//! - An [online client](crate::client::OnlineClient).
//! - An [offline client](crate::client::OfflineClient).
//! - A light client (which is currently still unstable).
//!
//! The [`crate::config::Config`] trait mimics the `frame_system::Config` trait and
//! subxt ships out of the box with two default implementations:
//! In theory it's possible for users to implement their own clients, although this isn't generally
//! expected.
//!
//! - [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and
//! - [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate.
//!
//! The latter will generally work in many cases, but [may need special customization](super::config) if
//! the node differs in any of the types the [`Config`](crate::config::Config) trait wants to know about.
//! The provided clients are all generic over the [`crate::config::Config`] that they accept, which
//! determines how they will interact with the chain.
//!
//! In the case of the [`crate::OnlineClient`], we have a few options to instantiate it:
//!
@@ -31,23 +27,20 @@
//!
//! The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this
//! allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other
//! than the provided interfaces.
//! than the provided interfaces. Under the hood, this is also how the light client is implemented.
//!
//! ## Examples
//!
//! Defining some custom config based off the default Substrate config:
//! Most of the other examples will instantiate a client. Here are a couple of examples for less common
//! cases.
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/setup_client_custom_config.rs")]
//! ```
//!
//! Writing a custom [`crate::rpc::RpcClientT`] implementation:
//! ### Writing a custom [`crate::rpc::RpcClientT`] implementation:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/setup_client_custom_rpc.rs")]
//! ```
//!
//! Creating an [`crate::OfflineClient`]:
//! ### Creating an [`crate::OfflineClient`]:
//!
//! ```rust,ignore
#![doc = include_str!("../../../examples/setup_client_offline.rs")]
+52 -58
View File
@@ -12,27 +12,21 @@
//! Some chains may use config that is not compatible with our [`PolkadotConfig`](crate::config::PolkadotConfig) or
//! [`SubstrateConfig`](crate::config::SubstrateConfig).
//!
//! We now walk through creating a [`crate::config::Config`] for a parachain, using the
//! We now walk through creating a custom [`crate::config::Config`] for a parachain, using the
//! ["Statemint"](https://parachains.info/details/statemint) parachain, also known as "Asset Hub", as an example. It
//! is currently (as of 2023-06-26) deployed on Polkadot and [Kusama (as "Statemine")](https://parachains.info/details/statemine).
//!
//! To construct a config, we need to investigate which types Statemint uses as `AccountId`, `Hasher`, etc.
//! We need to take a look at the source code of Statemint and find out how it implements some substrate functionalities.
//! Statemint (Polkadot Asset Hub) is part of the [Cumulus Github repository](https://github.com/paritytech/cumulus).
//! The crate defining the parachains runtime can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot).
//! To construct a valid [`crate::config::Config`] implementation, we need to find out which types to use for `AccountId`, `Hasher`, etc.
//! For this, we need to take a look at the source code of Statemint, which is currently a part of the [Cumulus Github repository](https://github.com/paritytech/cumulus).
//! The crate defining the asset hub runtime can be found [here](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot).
//!
//! ## Creating the `Config` from scratch
//!
//! Creating the config from scratch is the most arduous approach but also the most flexible, so first we'll walk through
//! how to do this, and then we'll show how to simplify the process where possible.
//!
//! ### AccountId, Hash, Hasher and Header
//! ## `AccountId`, `Hash`, `Hasher` and `Header`
//!
//! For these config types, we need to find out where the parachain runtime implements the `frame_system::Config` trait.
//! Look for a code fragment like `impl frame_system::Config for Runtime { ... }` In the source code.
//! For Statemint it looks like [this](https://github.com/paritytech/cumulus/blob/e2b7ad2061824f490c08df27a922c64f50accd6b/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L179)
//! at the time of writing. The `AccountId`, `Hash` and `Header` types of the [frame_system::pallet::Config](https://docs.rs/frame-system/latest/frame_system/pallet/trait.Config.html)
//! correspond to the ones we want to use for implementing [crate::Config]. In the Case of Statemint (Asset Hub) they are:
//! correspond to the ones we want to use in our Subxt [crate::Config]. In the Case of Statemint (Asset Hub) they are:
//!
//! - AccountId: `sp_core::crypto::AccountId32`
//! - Hash: `sp_core::H256`
@@ -49,17 +43,17 @@
//! Having a look at how those types are implemented can give some clues as to how to implement other custom types that
//! you may need to use as part of your config.
//!
//! ### Address, Signature
//! ## `Address`, `Signature`
//!
//! A Substrate runtime is typically constructed by using the [frame_support::construct_runtime](https://docs.rs/frame-support/latest/frame_support/macro.construct_runtime.html) macro.
//! In this macro, we need to specify the type of an `UncheckedExtrinsic`. Most of the time, the `UncheckedExtrinsic` will be of the type
//! `sp_runtime::generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>`.
//! The generic parameters `Address` and `Signature` specified when declaring the `UncheckedExtrinsic` type
//! are the types for `Address` and `Signature` we should use when implementing the [crate::Config] trait. This information can
//! are the types for `Address` and `Signature` we should use with our [crate::Config] implementation. This information can
//! also be obtained from the metadata (see [`frame_metadata::v15::ExtrinsicMetadata`]). In case of Statemint (Polkadot Asset Hub)
//! we see the following types being used in `UncheckedExtrinsic`:
//!
//! - Address: `sp_runtime::MultiAddress<Self::AccountId, ()>](sp_runtime::MultiAddress`
//! - Address: `sp_runtime::MultiAddress<Self::AccountId, ()>`
//! - Signature: `sp_runtime::MultiSignature`
//!
//! As above, Subxt has its own versions of these types that can be used instead to avoid pulling in Substrate dependencies.
@@ -68,7 +62,7 @@
//! - `sp_runtime::MultiAddress` can be swapped with [`crate::utils::MultiAddress`].
//! - `sp_runtime::MultiSignature` can be swapped with [`crate::utils::MultiSignature`].
//!
//! ### ExtrinsicParams
//! ## ExtrinsicParams
//!
//! Chains each have a set of "signed extensions" configured. Signed extensions provide a means to extend how transactions
//! work. Each signed extension can potentially encode some "extra" data which is sent along with a transaction, as well as some
@@ -77,12 +71,27 @@
//!
//! The `ExtrinsicParams` config type expects to be given an implementation of the [`crate::config::ExtrinsicParams`] trait.
//! Implementations of the [`crate::config::ExtrinsicParams`] trait are handed some parameters from Subxt itself, and can
//! accept arbitrary `OtherParams` from users, and are then expected to provide this "extra" and "additional" data when asked.
//! accept arbitrary `OtherParams` from users, and are then expected to provide this "extra" and "additional" data when asked
//! via the required [`crate::config::ExtrinsicParamsEncoder`] impl.
//!
//! In order to construct a valid implementation of the `ExtrinsicParams` trait, you must first find out which signed extensions
//! are in use by a chain. This information can be obtained from the `SignedExtra` parameter of the `UncheckedExtrinsic` of your
//! parachain, which will be a tuple of signed extensions. It can also be obtained from the metadata (see
//! [`frame_metadata::v15::SignedExtensionMetadata`]).
//! **In most cases, the default [crate::config::DefaultExtrinsicParams] type will work**: it understands the "standard"
//! signed extensions that are in use, and allows the user to provide things like a tip, and set the extrinsic mortality via
//! [`crate::config::DefaultExtrinsicParamsBuilder`]. It will use the chain metadata to decide which signed extensions to use
//! and in which order. It will return an error if the chain uses a signed extension which it doesn't know how to handle.
//!
//! If the chain uses novel signed extensions (or if you just wish to provide a different interface for users to configure
//! transactions), you can either:
//!
//! 1. Implement a new signed extension and add it to the list.
//! 2. Implement [`crate::config::DefaultExtrinsicParams`] from scratch.
//!
//! See below for examples of each.
//!
//! ### Finding out which signed extensions a chain is using.
//!
//! In either case, you'll want to find out which signed extensions a chain is using. This information can be obtained from
//! the `SignedExtra` parameter of the `UncheckedExtrinsic` of your parachain, which will be a tuple of signed extensions.
//! It can also be obtained from the metadata (see [`frame_metadata::v15::SignedExtensionMetadata`]).
//!
//! For statemint, the signed extensions look like
//! [this](https://github.com/paritytech/cumulus/tree/master/parachains/runtimes/assets/asset-hub-polkadot/src/lib.rs#L779):
@@ -116,45 +125,30 @@
//! | [`frame_system::ChargeAssetTxPayment`](https://docs.rs/frame-system/latest/frame_system/struct.ChargeAssetTxPayment.html) | [pallet_asset_tx_payment::ChargeAssetTxPayment](https://docs.rs/pallet-asset-tx-payment/latest/pallet_asset_tx_payment/struct.ChargeAssetTxPayment.html) | () |
//!
//! All types in the `struct type` column make up the "extra" data that we're expected to provide. All types in the
//! `AdditionalSigned` column make up the "additional" data that we're expected to provide. The goal of an
//! [`crate::config::ExtrinsicParams`] impl then is to provide the appropriate (SCALE encoded) data for these via
//! [`crate::config::ExtrinsicParams::encode_extra_to()`] and [`crate::config::ExtrinsicParams::encode_additional_to()`]
//! respectively. If the [`crate::config::ExtrinsicParams`] impl needs additional data to be able to do this, it can use
//! the [`crate::config::ExtrinsicParams::OtherParams`] associated type to obtain it from the user.
//! `AdditionalSigned` column make up the "additional" data that we're expected to provide. This information will be useful
//! whether we want to implement [`crate::config::SignedExtension`] for a signed extension, or implement
//! [`crate::config::ExtrinsicParams`] from scratch.
//!
//! Given the above information, here is a fairly naive approach to implementing config for Statemint, including the
//! [`crate::config::ExtrinsicParams`] trait, in a compatible way:
//! As it happens, all of the signed extensions in the table are either already exported in [`crate::config::signed_extensions`],
//! or they hand back no "additional" or "extra" data. In both of these cases, the default `ExtrinsicParams` configuration will
//! work out of the box.
//!
//! ### Implementing and adding new signed extensions to the config
//!
//! If you do need to implement a novel signed extension, then you can implement [`crate::config::signed_extensions::SignedExtension`]
//! on a custom type and place it into a new set of signed extensions, like so:
//!
//! ```rust,ignore
#![doc = include_str ! ("../../../examples/setup_config_signed_extension.rs")]
//! ```
//!
//! ### Implementing [`crate::config::ExtrinsicParams`] from scratch
//!
//! Alternately, you are free to implement [`crate::config::ExtrinsicParams`] entirely from scratch if you know exactly what "extra" and`
//! "additional" data your node needs and would prefer to craft your own interface.
//!
//! Let's see what this looks like (this config won't work on any real node):
//!
//! ```rust,ignore
#![doc = include_str ! ("../../../examples/setup_config_custom.rs")]
//! ```
//!
//! ## Using [`PolkadotConfig`](crate::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig) values to compose a Config
//!
//! Subxt already provides [`PolkadotConfig`](crate::config::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig). These
//! two configs are actually very similar, and only differ slightly in the `MultiAddress` type, and in terms of the type of `Tip` provided
//! as part of the `ExtrinsicParams`. Many chains share a lot of the same types as these, and so often times you can just reuse parts
//! of these implementations.
//!
//! From looking at the types needed for the Statemint config above, we can see that it is indeed very similar to these. It ultimately
//! uses the same signed extensions that Substrate uses, and otherwise uses the same types as Polkadot. So, we can create a config which
//! just reuses these existing types:
//!
//! ```rust,ignore
//! use subxt::{Config, PolkadotConfig, SubstrateConfig};
//!
//! pub enum StatemintConfig {}
//!
//! impl Config for StatemintConfig {
//! type Hash = <PolkadotConfig as Config>::Hash;
//! type AccountId = <PolkadotConfig as Config>::AccountId;
//! type Address = <PolkadotConfig as Config>::Address;
//! type Signature = <PolkadotConfig as Config>::Signature;
//! type Hasher = <PolkadotConfig as Config>::Hasher;
//! type Header = <PolkadotConfig as Config>::Header;
//! // this is the only difference to the PolkadotConfig:
//! type ExtrinsicParams = <SubstrateConfig as Config>::ExtrinsicParams;
//! }
//! ```
//!
//! When the types are very similar, building a custom config like this is much simpler than implementing one from scratch.
@@ -0,0 +1,144 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use super::{signed_extensions, ExtrinsicParams};
use super::{Config, Header};
/// The default [`super::ExtrinsicParams`] implementation understands common signed extensions
/// and how to apply them to a given chain.
pub type DefaultExtrinsicParams<T> = signed_extensions::AnyOf<
T,
(
signed_extensions::CheckSpecVersion,
signed_extensions::CheckTxVersion,
signed_extensions::CheckNonce,
signed_extensions::CheckGenesis<T>,
signed_extensions::CheckMortality<T>,
signed_extensions::ChargeAssetTxPayment,
signed_extensions::ChargeTransactionPayment,
),
>;
/// A builder that outputs the set of [`super::ExtrinsicParams::OtherParams`] required for
/// [`DefaultExtrinsicParams`]. This may expose methods that aren't applicable to the current
/// chain; such values will simply be ignored if so.
pub struct DefaultExtrinsicParamsBuilder<T: Config> {
/// `None` means the tx will be immortal.
mortality: Option<Mortality<T::Hash>>,
/// `None` means we'll use the native token.
tip_of_asset_id: Option<u32>,
tip: u128,
tip_of: u128,
}
struct Mortality<Hash> {
/// Block hash that mortality starts from
checkpoint_hash: Hash,
/// Block number that mortality starts from (must
// point to the same block as the hash above)
checkpoint_number: u64,
/// How many blocks the tx is mortal for
period: u64,
}
impl<T: Config> Default for DefaultExtrinsicParamsBuilder<T> {
fn default() -> Self {
Self {
mortality: None,
tip: 0,
tip_of: 0,
tip_of_asset_id: None,
}
}
}
impl<T: Config> DefaultExtrinsicParamsBuilder<T> {
/// Configure new extrinsic params. We default to providing no tip
/// and using an immortal transaction unless otherwise configured
pub fn new() -> Self {
Default::default()
}
/// Make the transaction mortal, given a block header that it should be mortal from,
/// and the number of blocks (roughly; it'll be rounded to a power of two) that it will
/// be mortal for.
pub fn mortal(mut self, from_block: &T::Header, for_n_blocks: u64) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block.hash(),
checkpoint_number: from_block.number().into(),
period: for_n_blocks,
});
self
}
/// Make the transaction mortal, given a block number and block hash (which must both point to
/// the same block) that it should be mortal from, and the number of blocks (roughly; it'll be
/// rounded to a power of two) that it will be mortal for.
///
/// Prefer to use [`DefaultExtrinsicParamsBuilder::mortal()`], which ensures that the block hash
/// and number align.
pub fn mortal_unchecked(
mut self,
from_block_number: u64,
from_block_hash: T::Hash,
for_n_blocks: u64,
) -> Self {
self.mortality = Some(Mortality {
checkpoint_hash: from_block_hash,
checkpoint_number: from_block_number,
period: for_n_blocks,
});
self
}
/// Provide a tip to the block author in the chain's native token.
pub fn tip(mut self, tip: u128) -> Self {
self.tip = tip;
self.tip_of = tip;
self.tip_of_asset_id = None;
self
}
/// Provide a tip to the block auther using the token denominated by the `asset_id` provided. This
/// is not applicable on chains which don't use the `ChargeAssetTxPayment` signed extension; in this
/// case, no tip will be given.
pub fn tip_of(mut self, tip: u128, asset_id: u32) -> Self {
self.tip = 0;
self.tip_of = tip;
self.tip_of_asset_id = Some(asset_id);
self
}
/// Build the extrinsic parameters.
pub fn build(self) -> <DefaultExtrinsicParams<T> as ExtrinsicParams<T>>::OtherParams {
let check_mortality_params = if let Some(mortality) = self.mortality {
signed_extensions::CheckMortalityParams::mortal(
mortality.period,
mortality.checkpoint_number,
mortality.checkpoint_hash,
)
} else {
signed_extensions::CheckMortalityParams::immortal()
};
let charge_asset_tx_params = if let Some(asset_id) = self.tip_of_asset_id {
signed_extensions::ChargeAssetTxPaymentParams::tip_of(self.tip, asset_id)
} else {
signed_extensions::ChargeAssetTxPaymentParams::tip(self.tip)
};
let charge_transaction_params =
signed_extensions::ChargeTransactionPaymentParams::tip(self.tip);
(
(),
(),
(),
(),
check_mortality_params,
charge_asset_tx_params,
charge_transaction_params,
)
}
}
+45 -241
View File
@@ -3,264 +3,68 @@
// see LICENSE for license details.
//! This module contains a trait which controls the parameters that must
//! be provided in order to successfully construct an extrinsic. A basic
//! implementation of the trait is provided ([`BaseExtrinsicParams`]) which is
//! used by the provided Substrate and Polkadot configuration.
//! be provided in order to successfully construct an extrinsic.
//! [`crate::config::DefaultExtrinsicParams`] provides a general-purpose
//! implementation of this that will work in many cases.
use crate::{utils::Encoded, Config};
use codec::{Compact, Decode, Encode};
use crate::{client::OfflineClientT, Config};
use core::fmt::Debug;
use derivative::Derivative;
use serde::{Deserialize, Serialize};
/// An error that can be emitted when trying to construct
/// an instance of [`ExtrinsicParams`].
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ExtrinsicParamsError {
/// A signed extension was encountered that we don't know about.
#[error("Error constructing extrinsic parameters: Unknown signed extension '{0}'")]
UnknownSignedExtension(String),
/// Some custom error.
#[error("Error constructing extrinsic parameters: {0}")]
Custom(CustomError),
}
/// A custom error.
type CustomError = Box<dyn std::error::Error + Send + Sync + 'static>;
impl From<std::convert::Infallible> for ExtrinsicParamsError {
fn from(value: std::convert::Infallible) -> Self {
match value {}
}
}
/// 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<Hash>: Debug + 'static {
/// "additional" parameters that are a part of the transaction payload
/// or the signer payload respectively.
pub trait ExtrinsicParams<T: Config>: ExtrinsicParamsEncoder + Sized + '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: u64,
genesis_hash: Hash,
other_params: Self::OtherParams,
) -> Self;
/// The type of error returned from [`ExtrinsicParams::new()`].
type Error: Into<ExtrinsicParamsError>;
/// Construct a new instance of our [`ExtrinsicParams`]
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error>;
}
/// This trait is expected to be implemented for any [`ExtrinsicParams`], and
/// defines how to encode the "additional" and "extra" params. Both functions
/// are optional and will encode nothing by default.
pub trait ExtrinsicParamsEncoder: 'static {
/// 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>);
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>);
}
/// 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 defines how long the transaction will be valid for.
era: Era,
/// Nonce (account nonce sent along an extrinsic, such that no extrinsic is submitted twice)
nonce: u64,
/// The tip you would like to give to the block author for this transaction.
/// Note: Can be zero.
tip: Tip,
/// spec version
spec_version: u32,
/// transaction version
transaction_version: u32,
/// genesis hash of the chain
genesis_hash: T::Hash,
/// The block after which the transaction becomes valid.
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 [`super::substrate::SubstrateExtrinsicParamsBuilder`] for a version of this
/// tailored towards Substrate, or [`super::polkadot::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::Hash>
for BaseExtrinsicParams<T, Tip>
{
type OtherParams = BaseExtrinsicParamsBuilder<T, Tip>;
fn new(
// Provided from subxt client:
spec_version: u32,
transaction_version: u32,
nonce: u64,
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;
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);
}
}
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
/// An era to describe the longevity of a transaction.
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum Era {
/// The transaction is valid forever. The genesis hash must be present in the signed content.
Immortal,
/// Period and phase are encoded:
/// - The period of validity from the block hash found in the signing material.
/// - The phase in the period that this transaction's lifetime begins (and, importantly,
/// implies which block hash is included in the signature material). If the `period` is
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
/// `period` is.
///
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
/// of `system` module.
Mortal(Period, Phase),
}
/// Era period
pub type Period = u64;
/// Era phase
pub type Phase = u64;
// E.g. with period == 4:
// 0 10 20 30 40
// 0123456789012345678901234567890123456789012
// |...|
// authored -/ \- expiry
// phase = 1
// n = Q(current - phase, period) + phase
impl Era {
/// Create a new era based on a period (which should be a power of two between 4 and 65536
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
/// after the start).
///
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
/// prunes old blocks and renders transactions immediately invalid.
pub fn mortal(period: u64, current: u64) -> Self {
let period = period
.checked_next_power_of_two()
.unwrap_or(1 << 16)
.clamp(4, 1 << 16);
let phase = current % period;
let quantize_factor = (period >> 12).max(1);
let quantized_phase = phase / quantize_factor * quantize_factor;
Self::Mortal(period, quantized_phase)
}
/// Create an "immortal" transaction.
pub fn immortal() -> Self {
Self::Immortal
}
}
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
// it's really the most important bit here.
impl Encode for Era {
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
match self {
Self::Immortal => output.push_byte(0),
Self::Mortal(period, phase) => {
let quantize_factor = (*period >> 12).max(1);
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
| ((phase / quantize_factor) << 4) as u16;
encoded.encode_to(output);
}
}
}
}
impl Decode for Era {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let first = input.read_byte()?;
if first == 0 {
Ok(Self::Immortal)
} else {
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
let period = 2 << (encoded % (1 << 4));
let quantize_factor = (period >> 12).max(1);
let phase = (encoded >> 4) * quantize_factor;
if period >= 4 && phase < period {
Ok(Self::Mortal(period, phase))
} else {
Err("Invalid period and phase".into())
}
}
}
fn encode_additional_to(&self, _v: &mut Vec<u8>) {}
}
+14 -25
View File
@@ -8,23 +8,28 @@
//! default Substrate node implementation, and [`PolkadotConfig`] for a
//! Polkadot node.
pub mod extrinsic_params;
mod default_extrinsic_params;
mod extrinsic_params;
pub mod polkadot;
pub mod signed_extensions;
pub mod substrate;
use codec::{Decode, Encode};
use core::fmt::Debug;
use serde::{de::DeserializeOwned, Serialize};
pub use extrinsic_params::ExtrinsicParams;
pub use polkadot::PolkadotConfig;
pub use substrate::SubstrateConfig;
pub use default_extrinsic_params::{DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
pub use polkadot::{PolkadotConfig, PolkadotExtrinsicParams, PolkadotExtrinsicParamsBuilder};
pub use signed_extensions::SignedExtension;
pub use substrate::{SubstrateConfig, SubstrateExtrinsicParams, SubstrateExtrinsicParamsBuilder};
/// Runtime types.
// Note: the 'static bound isn't strictly required, but currently deriving TypeInfo
// automatically applies a 'static bound to all generic types (including this one),
// and so until that is resolved, we'll keep the (easy to satisfy) constraint here.
pub trait Config: 'static {
pub trait Config: Sized + 'static {
/// The output of the `Hasher` function.
type Hash: Debug
+ Copy
@@ -53,9 +58,12 @@ pub trait Config: 'static {
type Header: Debug + Header<Hasher = Self::Hasher> + Sync + Send + DeserializeOwned;
/// This type defines the extrinsic extra and additional parameters.
type ExtrinsicParams: extrinsic_params::ExtrinsicParams<Self::Hash>;
type ExtrinsicParams: ExtrinsicParams<Self>;
}
/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`.
pub type OtherParamsFor<T> = <<T as Config>::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams;
/// This represents the hasher used by a node to hash things like block headers
/// and extrinsics.
pub trait Hasher {
@@ -88,25 +96,6 @@ pub trait Header: Sized + Encode {
}
}
/// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the
/// additional and extra parameters to pass to an extrinsic (see [`ExtrinsicParams`]),
/// and returns a type implementing [`Config`] with those new [`ExtrinsicParams`].
pub struct WithExtrinsicParams<T: Config, E: extrinsic_params::ExtrinsicParams<T::Hash>> {
_marker: std::marker::PhantomData<(T, E)>,
}
impl<T: Config, E: extrinsic_params::ExtrinsicParams<T::Hash>> Config
for WithExtrinsicParams<T, E>
{
type Hash = T::Hash;
type AccountId = T::AccountId;
type Address = T::Address;
type Signature = T::Signature;
type Hasher = T::Hasher;
type Header = T::Header;
type ExtrinsicParams = E;
}
/// implement subxt's Hasher and Header traits for some substrate structs
#[cfg(feature = "substrate-compat")]
mod substrate_impls {
+3 -30
View File
@@ -4,11 +4,7 @@
//! Polkadot specific configuration
use super::{
extrinsic_params::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder},
Config,
};
use codec::Encode;
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder};
pub use crate::utils::{AccountId32, MultiAddress, MultiSignature};
use crate::SubstrateConfig;
@@ -29,31 +25,8 @@ impl Config for PolkadotConfig {
/// 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>;
pub type PolkadotExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// 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>;
// Because Era is one of the args to our extrinsic params.
pub use super::extrinsic_params::Era;
/// 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)
}
}
pub type PolkadotExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
+453
View File
@@ -0,0 +1,453 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! This module contains implementations for common signed extensions, each
//! of which implements [`SignedExtension`], and can be used in conjunction with
//! [`AnyOf`] to configure the set of signed extensions which are known about
//! when interacting with a chain.
use super::extrinsic_params::{ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError};
use crate::utils::Era;
use crate::{client::OfflineClientT, Config};
use codec::{Compact, Encode};
use core::fmt::Debug;
use std::collections::HashMap;
/// A single [`SignedExtension`] has a unique name, but is otherwise the
/// same as [`ExtrinsicParams`] in describing how to encode the extra and
/// additional data.
pub trait SignedExtension<T: Config>: ExtrinsicParams<T> {
/// The name of the signed extension. This is used to associate it
/// with the signed extensions that the node is making use of.
const NAME: &'static str;
}
/// The [`CheckSpecVersion`] signed extension.
#[derive(Debug)]
pub struct CheckSpecVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckSpecVersion {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CheckSpecVersion(client.runtime_version().spec_version))
}
}
impl ExtrinsicParamsEncoder for CheckSpecVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckSpecVersion {
const NAME: &'static str = "CheckSpecVersion";
}
/// The [`CheckNonce`] signed extension.
#[derive(Debug)]
pub struct CheckNonce(Compact<u64>);
impl<T: Config> ExtrinsicParams<T> for CheckNonce {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
nonce: u64,
_client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CheckNonce(Compact(nonce)))
}
}
impl ExtrinsicParamsEncoder for CheckNonce {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckNonce {
const NAME: &'static str = "CheckNonce";
}
/// The [`CheckTxVersion`] signed extension.
#[derive(Debug)]
pub struct CheckTxVersion(u32);
impl<T: Config> ExtrinsicParams<T> for CheckTxVersion {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CheckTxVersion(client.runtime_version().transaction_version))
}
}
impl ExtrinsicParamsEncoder for CheckTxVersion {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckTxVersion {
const NAME: &'static str = "CheckTxVersion";
}
/// The [`CheckGenesis`] signed extension.
pub struct CheckGenesis<T: Config>(T::Hash);
impl<T: Config> std::fmt::Debug for CheckGenesis<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("CheckGenesis").field(&self.0).finish()
}
}
impl<T: Config> ExtrinsicParams<T> for CheckGenesis<T> {
type OtherParams = ();
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
_other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CheckGenesis(client.genesis_hash()))
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckGenesis<T> {
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.0.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for CheckGenesis<T> {
const NAME: &'static str = "CheckGenesis";
}
/// The [`CheckMortality`] signed extension.
pub struct CheckMortality<T: Config> {
era: Era,
checkpoint: T::Hash,
}
/// Parameters to configure the [`CheckMortality`] signed extension.
pub struct CheckMortalityParams<T: Config> {
era: Era,
checkpoint: Option<T::Hash>,
}
impl<T: Config> Default for CheckMortalityParams<T> {
fn default() -> Self {
Self {
era: Default::default(),
checkpoint: Default::default(),
}
}
}
impl<T: Config> CheckMortalityParams<T> {
/// Configure a mortal transaction. The `period` is (roughly) how many
/// blocks the transaction will be valid for. The `block_number` and
/// `block_hash` should both point to the same block, and are the block that
/// the transaction is mortal from.
pub fn mortal(period: u64, block_number: u64, block_hash: T::Hash) -> Self {
CheckMortalityParams {
era: Era::mortal(period, block_number),
checkpoint: Some(block_hash),
}
}
/// An immortal transaction.
pub fn immortal() -> Self {
CheckMortalityParams {
era: Era::Immortal,
checkpoint: None,
}
}
}
impl<T: Config> std::fmt::Debug for CheckMortality<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CheckMortality")
.field("era", &self.era)
.field("checkpoint", &self.checkpoint)
.finish()
}
}
impl<T: Config> ExtrinsicParams<T> for CheckMortality<T> {
type OtherParams = CheckMortalityParams<T>;
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(CheckMortality {
era: other_params.era,
checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()),
})
}
}
impl<T: Config> ExtrinsicParamsEncoder for CheckMortality<T> {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.era.encode_to(v);
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
self.checkpoint.encode_to(v)
}
}
impl<T: Config> SignedExtension<T> for CheckMortality<T> {
const NAME: &'static str = "CheckMortality";
}
/// The [`ChargeAssetTxPayment`] signed extension.
#[derive(Debug)]
pub struct ChargeAssetTxPayment {
tip: Compact<u128>,
asset_id: Option<u32>,
}
/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension.
#[derive(Default)]
pub struct ChargeAssetTxPaymentParams {
tip: u128,
asset_id: Option<u32>,
}
impl ChargeAssetTxPaymentParams {
/// Don't provide a tip to the extrinsic author.
pub fn no_tip() -> Self {
ChargeAssetTxPaymentParams {
tip: 0,
asset_id: None,
}
}
/// Tip the extrinsic author in the native chain token.
pub fn tip(tip: u128) -> Self {
ChargeAssetTxPaymentParams {
tip,
asset_id: None,
}
}
/// Tip the extrinsic author using the asset ID given.
pub fn tip_of(tip: u128, asset_id: u32) -> Self {
ChargeAssetTxPaymentParams {
tip,
asset_id: Some(asset_id),
}
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeAssetTxPayment {
type OtherParams = ChargeAssetTxPaymentParams;
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(ChargeAssetTxPayment {
tip: Compact(other_params.tip),
asset_id: other_params.asset_id,
})
}
}
impl ExtrinsicParamsEncoder for ChargeAssetTxPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
(self.tip, self.asset_id).encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment {
const NAME: &'static str = "ChargeAssetTxPayment";
}
/// The [`ChargeTransactionPayment`] signed extension.
#[derive(Debug)]
pub struct ChargeTransactionPayment {
tip: Compact<u128>,
}
/// Parameters to configure the [`ChargeTransactionPayment`] signed extension.
#[derive(Default)]
pub struct ChargeTransactionPaymentParams {
tip: u128,
}
impl ChargeTransactionPaymentParams {
/// Don't provide a tip to the extrinsic author.
pub fn no_tip() -> Self {
ChargeTransactionPaymentParams { tip: 0 }
}
/// Tip the extrinsic author in the native chain token.
pub fn tip(tip: u128) -> Self {
ChargeTransactionPaymentParams { tip }
}
}
impl<T: Config> ExtrinsicParams<T> for ChargeTransactionPayment {
type OtherParams = ChargeTransactionPaymentParams;
type Error = std::convert::Infallible;
fn new<Client: OfflineClientT<T>>(
_nonce: u64,
_client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
Ok(ChargeTransactionPayment {
tip: Compact(other_params.tip),
})
}
}
impl ExtrinsicParamsEncoder for ChargeTransactionPayment {
fn encode_extra_to(&self, v: &mut Vec<u8>) {
self.tip.encode_to(v);
}
}
impl<T: Config> SignedExtension<T> for ChargeTransactionPayment {
const NAME: &'static str = "ChargeTransactionPayment";
}
/// This accepts a tuple of [`SignedExtension`]s, and will dynamically make use of whichever
/// ones are actually required for the chain in the correct order, ignoring the rest. This
/// is a sensible default, and allows for a single configuration to work across multiple chains.
pub struct AnyOf<T, Params> {
params: Vec<Box<dyn ExtrinsicParamsEncoder>>,
_marker: std::marker::PhantomData<(T, Params)>,
}
macro_rules! impl_tuples {
($($ident:ident $index:tt),+) => {
// We do some magic when the tuple is wrapped in AnyOf. We
// look at the metadata, and use this to select and make use of only the extensions
// that we actually need for the chain we're dealing with.
impl <T, $($ident),+> ExtrinsicParams<T> for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: SignedExtension<T>,)+
{
type OtherParams = ($($ident::OtherParams,)+);
type Error = ExtrinsicParamsError;
fn new<Client: OfflineClientT<T>>(
nonce: u64,
client: Client,
other_params: Self::OtherParams,
) -> Result<Self, Self::Error> {
// First, push encoders to map as we are given them:
let mut map = HashMap::new();
$({
let e: Box<dyn ExtrinsicParamsEncoder>
= Box::new($ident::new(nonce, client.clone(), other_params.$index).map_err(Into::into)?);
map.insert($ident::NAME, e);
})+
// Next, based on metadata, push to vec in the order the node needs:
let mut params = Vec::new();
let metadata = client.metadata();
let types = metadata.types();
for ext in metadata.extrinsic().signed_extensions() {
if let Some(ext) = map.remove(ext.identifier()) {
params.push(ext)
} else {
if is_type_empty(ext.extra_ty(), types) && is_type_empty(ext.additional_ty(), types) {
// If we don't know about the signed extension, _but_ it appears to require zero bytes
// to encode its extra and additional data, then we can safely ignore it as it makes
// no difference either way.
continue;
}
return Err(ExtrinsicParamsError::UnknownSignedExtension(ext.identifier().to_owned()));
}
}
Ok(AnyOf {
params,
_marker: std::marker::PhantomData
})
}
}
impl <T, $($ident),+> ExtrinsicParamsEncoder for AnyOf<T, ($($ident,)+)>
where
T: Config,
$($ident: SignedExtension<T>,)+
{
fn encode_extra_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_extra_to(v);
}
}
fn encode_additional_to(&self, v: &mut Vec<u8>) {
for ext in &self.params {
ext.encode_additional_to(v);
}
}
}
}
}
#[rustfmt::skip]
const _: () = {
impl_tuples!(A 0);
impl_tuples!(A 0, B 1);
impl_tuples!(A 0, B 1, C 2);
impl_tuples!(A 0, B 1, C 2, D 3);
impl_tuples!(A 0, B 1, C 2, D 3, E 4);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19);
impl_tuples!(A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7, I 8, J 9, K 10, L 11, M 12, N 13, O 14, P 15, Q 16, R 17, S 18, U 19, V 20);
};
/// Checks to see whether the type being given is empty, ie would require
/// 0 bytes to encode.
fn is_type_empty(type_id: u32, types: &scale_info::PortableRegistry) -> bool {
let Some(ty) = types.resolve(type_id) else {
// Can't resolve; type may not be empty. Not expected to hit this.
return false
};
use scale_info::TypeDef;
match &ty.type_def {
TypeDef::Composite(c) => c.fields.iter().all(|f| is_type_empty(f.ty.id, types)),
TypeDef::Array(a) => a.len == 0 || is_type_empty(a.type_param.id, types),
TypeDef::Tuple(t) => t.fields.iter().all(|f| is_type_empty(f.id, types)),
// Explicitly list these in case any additions are made in the future.
TypeDef::BitSequence(_)
| TypeDef::Variant(_)
| TypeDef::Sequence(_)
| TypeDef::Compact(_)
| TypeDef::Primitive(_) => false,
}
}
+3 -40
View File
@@ -4,10 +4,7 @@
//! Substrate specific configuration
use super::{
extrinsic_params::{BaseExtrinsicParams, BaseExtrinsicParamsBuilder},
Config, Hasher, Header,
};
use super::{Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, Hasher, Header};
use codec::{Decode, Encode};
use serde::{Deserialize, Serialize};
@@ -31,45 +28,11 @@ impl Config for SubstrateConfig {
/// 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>;
pub type SubstrateExtrinsicParams<T> = DefaultExtrinsicParams<T>;
/// 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>;
// Because Era is one of the args to our extrinsic params.
pub use super::extrinsic_params::Era;
/// 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)
}
}
pub type SubstrateExtrinsicParamsBuilder<T> = DefaultExtrinsicParamsBuilder<T>;
/// A type that can hash values using the blaks2_256 algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)]
+10
View File
@@ -17,6 +17,7 @@ pub use dispatch_error::{
};
// Re-expose the errors we use from other crates here:
pub use crate::config::ExtrinsicParamsError;
pub use crate::metadata::Metadata;
pub use scale_decode::Error as DecodeError;
pub use scale_encode::Error as EncodeError;
@@ -58,6 +59,9 @@ pub enum Error {
/// Transaction progress error.
#[error("Transaction error: {0}")]
Transaction(#[from] TransactionError),
/// Error constructing the appropriate extrinsic params.
#[error("{0}")]
ExtrinsicParams(#[from] ExtrinsicParamsError),
/// Block related error.
#[error("Block error: {0}")]
Block(#[from] BlockError),
@@ -88,6 +92,12 @@ impl From<String> for Error {
}
}
impl From<std::convert::Infallible> for Error {
fn from(value: std::convert::Infallible) -> Self {
match value {}
}
}
/// An RPC error. Since we are generic over the RPC client that is used,
/// the error is boxed and could be casted.
#[derive(Debug, thiserror::Error)]
+15 -20
View File
@@ -11,7 +11,7 @@ use sp_core_hashing::blake2_256;
use crate::error::DecodeError;
use crate::{
client::{OfflineClientT, OnlineClientT},
config::{Config, ExtrinsicParams, Hasher},
config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, Hasher},
error::{Error, MetadataError},
tx::{Signer as SignerT, TxPayload, TxProgress},
utils::{Encoded, PhantomDataSendSync},
@@ -111,7 +111,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
&self,
call: &Call,
account_nonce: u64,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
) -> Result<PartialExtrinsic<T, C>, Error>
where
Call: TxPayload,
@@ -124,17 +124,12 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
let call_data = self.call_data(call)?;
// 3. Construct our custom additional/extra params.
let additional_and_extra_params = {
// Obtain spec version and transaction version from the runtime version of the client.
let runtime = self.client.runtime_version();
<T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::new(
runtime.spec_version,
runtime.transaction_version,
account_nonce,
self.client.genesis_hash(),
other_params,
)
};
let additional_and_extra_params = <T::ExtrinsicParams as ExtrinsicParams<T>>::new(
account_nonce,
self.client.clone(),
other_params,
)
.map_err(Into::into)?;
// Return these details, ready to construct a signed extrinsic from.
Ok(PartialExtrinsic {
@@ -150,7 +145,7 @@ impl<T: Config, C: OfflineClientT<T>> TxClient<T, C> {
call: &Call,
signer: &Signer,
account_nonce: u64,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
@@ -203,7 +198,7 @@ where
&self,
call: &Call,
account_id: &T::AccountId,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
) -> Result<PartialExtrinsic<T, C>, Error>
where
Call: TxPayload,
@@ -217,7 +212,7 @@ where
&self,
call: &Call,
signer: &Signer,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
) -> Result<SubmittableExtrinsic<T, C>, Error>
where
Call: TxPayload,
@@ -240,7 +235,7 @@ where
where
Call: TxPayload,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams: Default,
<T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams: Default,
{
self.sign_and_submit_then_watch(call, signer, Default::default())
.await
@@ -254,7 +249,7 @@ where
&self,
call: &Call,
signer: &Signer,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
) -> Result<TxProgress<T, C>, Error>
where
Call: TxPayload,
@@ -284,7 +279,7 @@ where
where
Call: TxPayload,
Signer: SignerT<T>,
<T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams: Default,
<T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams: Default,
{
self.sign_and_submit(call, signer, Default::default()).await
}
@@ -301,7 +296,7 @@ where
&self,
call: &Call,
signer: &Signer,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T::Hash>>::OtherParams,
other_params: <T::ExtrinsicParams as ExtrinsicParams<T>>::OtherParams,
) -> Result<T::Hash, Error>
where
Call: TxPayload,
+6 -10
View File
@@ -419,7 +419,6 @@ mod test {
use crate::{
client::{OfflineClientT, OnlineClientT},
config::{extrinsic_params::BaseExtrinsicParams, polkadot::PlainTip, WithExtrinsicParams},
error::RpcError,
rpc::{types::SubstrateTxStatus, RpcSubscription, Subscription},
tx::TxProgress,
@@ -429,10 +428,7 @@ mod test {
use serde_json::value::RawValue;
type MockTxProgress = TxProgress<SubstrateConfig, MockClient>;
type MockHash = <WithExtrinsicParams<
SubstrateConfig,
BaseExtrinsicParams<SubstrateConfig, PlainTip>,
> as Config>::Hash;
type MockHash = <SubstrateConfig as Config>::Hash;
type MockSubstrateTxStatus = SubstrateTxStatus<MockHash, MockHash>;
/// a mock client to satisfy trait bounds in tests
@@ -441,21 +437,21 @@ mod test {
impl OfflineClientT<SubstrateConfig> for MockClient {
fn metadata(&self) -> crate::Metadata {
panic!("just a mock impl to satisfy trait bounds")
unimplemented!("just a mock impl to satisfy trait bounds")
}
fn genesis_hash(&self) -> <SubstrateConfig as crate::Config>::Hash {
panic!("just a mock impl to satisfy trait bounds")
fn genesis_hash(&self) -> MockHash {
unimplemented!("just a mock impl to satisfy trait bounds")
}
fn runtime_version(&self) -> crate::rpc::types::RuntimeVersion {
panic!("just a mock impl to satisfy trait bounds")
unimplemented!("just a mock impl to satisfy trait bounds")
}
}
impl OnlineClientT<SubstrateConfig> for MockClient {
fn rpc(&self) -> &crate::rpc::Rpc<SubstrateConfig> {
panic!("just a mock impl to satisfy trait bounds")
unimplemented!("just a mock impl to satisfy trait bounds")
}
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
// Dev note: This and related bits taken from `sp_runtime::generic::Era`
/// An era to describe the longevity of a transaction.
#[derive(PartialEq, Default, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum Era {
/// The transaction is valid forever. The genesis hash must be present in the signed content.
#[default]
Immortal,
/// The transaction will expire. Use [`Era::mortal`] to construct this with correct values.
///
/// When used on `FRAME`-based runtimes, `period` cannot exceed `BlockHashCount` parameter
/// of `system` module.
Mortal {
/// The number of blocks that the tx will be valid for after the checkpoint block
/// hash found in the signer payload.
period: u64,
/// The phase in the period that this transaction's lifetime begins (and, importantly,
/// implies which block hash is included in the signature material). If the `period` is
/// greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
/// `period` is.
phase: u64,
},
}
// E.g. with period == 4:
// 0 10 20 30 40
// 0123456789012345678901234567890123456789012
// |...|
// authored -/ \- expiry
// phase = 1
// n = Q(current - phase, period) + phase
impl Era {
/// Create a new era based on a period (which should be a power of two between 4 and 65536
/// inclusive) and a block number on which it should start (or, for long periods, be shortly
/// after the start).
///
/// If using `Era` in the context of `FRAME` runtime, make sure that `period`
/// does not exceed `BlockHashCount` parameter passed to `system` module, since that
/// prunes old blocks and renders transactions immediately invalid.
pub fn mortal(period: u64, current: u64) -> Self {
let period = period
.checked_next_power_of_two()
.unwrap_or(1 << 16)
.clamp(4, 1 << 16);
let phase = current % period;
let quantize_factor = (period >> 12).max(1);
let quantized_phase = phase / quantize_factor * quantize_factor;
Self::Mortal {
period,
phase: quantized_phase,
}
}
}
// Both copied from `sp_runtime::generic::Era`; this is the wire interface and so
// it's really the most important bit here.
impl codec::Encode for Era {
fn encode_to<T: codec::Output + ?Sized>(&self, output: &mut T) {
match self {
Self::Immortal => output.push_byte(0),
Self::Mortal { period, phase } => {
let quantize_factor = (*period >> 12).max(1);
let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16
| ((phase / quantize_factor) << 4) as u16;
encoded.encode_to(output);
}
}
}
}
impl codec::Decode for Era {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let first = input.read_byte()?;
if first == 0 {
Ok(Self::Immortal)
} else {
let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
let period = 2 << (encoded % (1 << 4));
let quantize_factor = (period >> 12).max(1);
let phase = (encoded >> 4) * quantize_factor;
if period >= 4 && phase < period {
Ok(Self::Mortal { period, phase })
} else {
Err("Invalid period and phase".into())
}
}
}
}
+2
View File
@@ -6,6 +6,7 @@
mod account_id;
pub mod bits;
mod era;
mod multi_address;
mod multi_signature;
mod static_type;
@@ -16,6 +17,7 @@ use codec::{Compact, Decode, Encode};
use derivative::Derivative;
pub use account_id::AccountId32;
pub use era::Era;
pub use multi_address::MultiAddress;
pub use multi_signature::MultiSignature;
pub use static_type::Static;