mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
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:
Generated
+44
-44
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user