diff --git a/examples/wasm-example/Cargo.lock b/examples/wasm-example/Cargo.lock index 63aea8e290..174decfb19 100644 --- a/examples/wasm-example/Cargo.lock +++ b/examples/wasm-example/Cargo.lock @@ -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", ] diff --git a/examples/wasm-example/src/routes/signing.rs b/examples/wasm-example/src/routes/signing.rs index 44fe024a56..64b0be7e01 100644 --- a/examples/wasm-example/src/routes/signing.rs +++ b/examples/wasm-example/src/routes/signing.rs @@ -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(signature) = extension_signature_for_partial_extrinsic(&partial_extrinsic, &api, &account_id, account_source, account_address).await else { + let Ok(account_nonce) = api.tx().account_nonce(&account_id).await else { + return Message::Error(anyhow!("Fetching account nonce failed")); + }; + + 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), diff --git a/examples/wasm-example/src/services.rs b/examples/wasm-example/src/services.rs index b1bd978527..59e0ca2b1e 100644 --- a/examples/wasm-example/src/services.rs +++ b/examples/wasm-example/src/services.rs @@ -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(input: &E) -> String { +fn encode_then_hex(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(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>, +pub async fn extension_signature_for_extrinsic( + call_data: &[u8], api: &OnlineClient, - account_id: &AccountId32, + account_nonce: u64, account_source: String, account_address: String, ) -> Result, 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 = 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, diff --git a/subxt/examples/setup_client_custom_config.rs b/subxt/examples/setup_client_custom_config.rs deleted file mode 100644 index 7ee30ba0a7..0000000000 --- a/subxt/examples/setup_client_custom_config.rs +++ /dev/null @@ -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 = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; - type AccountId = ::AccountId; - type Address = ::Address; - type Signature = ::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; -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - // Create a client which uses the custom config: - let _api = OnlineClient::::new().await?; - - Ok(()) -} diff --git a/subxt/examples/setup_config_custom.rs b/subxt/examples/setup_config_custom.rs index d5edcc0490..46a3afc3a2 100644 --- a/subxt/examples/setup_config_custom.rs +++ b/subxt/examples/setup_config_custom.rs @@ -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; type Signature = subxt::utils::MultiSignature; type Hasher = subxt::config::substrate::BlakeTwo256; type Header = subxt::config::substrate::SubstrateHeader; - type ExtrinsicParams = StatemintExtrinsicParams; + type ExtrinsicParams = CustomExtrinsicParams; } -#[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 { + genesis_hash: T::Hash, + tip: u128, + foo: bool, } -impl ExtrinsicParams 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, - ); - - // Gather together all of the params we will need to encode: - fn new( - spec_version: u32, - tx_version: u32, - nonce: u64, - genesis_hash: H256, - 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, - }; +// We can provide a "pretty" interface to allow users to provide these: +#[derive(Default)] +pub struct CustomExtrinsicParamsBuilder { + tip: u128, + foo: bool, +} - Self { - extra_params, - additional_params, - } +impl CustomExtrinsicParamsBuilder { + pub fn new() -> Self { + Default::default() } - - // Encode the relevant params when asked: - fn encode_extra_to(&self, v: &mut Vec) { - self.extra_params.encode_to(v); + pub fn tip(mut self, value: u128) -> Self { + self.tip = value; + self } - fn encode_additional_to(&self, v: &mut Vec) { - self.additional_params.encode_to(v); + pub fn enable_foo(mut self) -> Self { + self.foo = true; + self } } -#[derive(Encode, Debug, Clone, Eq, PartialEq)] -pub struct StatemintExtraParams { - era: sp_runtime::generic::Era, - nonce: u64, - charge: ChargeAssetTxPayment, -} +// Describe how to fetch and then encode the params: +impl ExtrinsicParams for CustomExtrinsicParams { + type OtherParams = CustomExtrinsicParamsBuilder; + type Error = std::convert::Infallible; -#[derive(Encode, Debug, Clone, Eq, PartialEq)] -pub struct ChargeAssetTxPayment { - #[codec(compact)] - tip: u128, - asset_id: Option, + // Gather together all of the params we will need to encode: + fn new>( + _nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(Self { + genesis_hash: client.genesis_hash(), + tip: other_params.tip, + foo: other_params.foo, + }) + } } -#[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, +// Encode the relevant params when asked: +impl ExtrinsicParamsEncoder for CustomExtrinsicParams { + fn encode_extra_to(&self, v: &mut Vec) { + (self.tip, self.foo).encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + self.genesis_hash.encode_to(v) + } } #[tokio::main] async fn main() { // With the config defined, it can be handed to Subxt as follows: - let _client_fut = subxt::OnlineClient::::new(); + let client = subxt::OnlineClient::::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; } diff --git a/subxt/examples/setup_config_signed_extension.rs b/subxt/examples/setup_config_signed_extension.rs new file mode 100644 index 0000000000..38e871bfa1 --- /dev/null +++ b/subxt/examples/setup_config_signed_extension.rs @@ -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; + type Signature = subxt::utils::MultiSignature; + type Hasher = subxt::config::substrate::BlakeTwo256; + type Header = subxt::config::substrate::SubstrateHeader; + 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, + signed_extensions::CheckMortality, + 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 signed_extensions::SignedExtension for CustomSignedExtension { + const NAME: &'static str = "CustomSignedExtension"; +} + +// Gather together any params we need for our signed extension, here none. +impl ExtrinsicParams for CustomSignedExtension { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + _client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CustomSignedExtension) + } +} + +// Encode whatever the extension needs to provide when asked: +impl ExtrinsicParamsEncoder for CustomSignedExtension { + fn encode_extra_to(&self, v: &mut Vec) { + "Hello".encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + 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, +) -> <::ExtrinsicParams as ExtrinsicParams>::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::::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; +} diff --git a/subxt/examples/tx_with_params.rs b/subxt/examples/tx_with_params.rs index f1295d0810..90a02430b0 100644 --- a/subxt/examples/tx_with_params.rs +++ b/subxt/examples/tx_with_params.rs @@ -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> { 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(); diff --git a/subxt/src/book/setup/client.rs b/subxt/src/book/setup/client.rs index 9329d7839a..fb1567d687 100644 --- a/subxt/src/book/setup/client.rs +++ b/subxt/src/book/setup/client.rs @@ -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: -//! -//! ```rust,ignore -#![doc = include_str!("../../../examples/setup_client_custom_config.rs")] -//! ``` +//! Most of the other examples will instantiate a client. Here are a couple of examples for less common +//! cases. //! -//! 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")] diff --git a/subxt/src/book/setup/config.rs b/subxt/src/book/setup/config.rs index 85cbf84dfa..dba7654d7b 100644 --- a/subxt/src/book/setup/config.rs +++ b/subxt/src/book/setup/config.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`. //! 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](sp_runtime::MultiAddress` +//! - Address: `sp_runtime::MultiAddress` //! - 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 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. //! -//! 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`]). +//! ### 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. +//! +//! 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 //! -//! 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: +//! 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_custom.rs")] +#![doc = include_str ! ("../../../examples/setup_config_signed_extension.rs")] //! ``` //! -//! ## Using [`PolkadotConfig`](crate::PolkadotConfig) and [`SubstrateConfig`](crate::SubstrateConfig) values to compose a Config +//! ### Implementing [`crate::config::ExtrinsicParams`] from scratch //! -//! 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. +//! 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. //! -//! 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: +//! Let's see what this looks like (this config won't work on any real node): //! //! ```rust,ignore -//! use subxt::{Config, PolkadotConfig, SubstrateConfig}; -//! -//! pub enum StatemintConfig {} -//! -//! impl Config for StatemintConfig { -//! type Hash = ::Hash; -//! type AccountId = ::AccountId; -//! type Address = ::Address; -//! type Signature = ::Signature; -//! type Hasher = ::Hasher; -//! type Header = ::Header; -//! // this is the only difference to the PolkadotConfig: -//! type ExtrinsicParams = ::ExtrinsicParams; -//! } +#![doc = include_str ! ("../../../examples/setup_config_custom.rs")] //! ``` -//! -//! When the types are very similar, building a custom config like this is much simpler than implementing one from scratch. diff --git a/subxt/src/config/default_extrinsic_params.rs b/subxt/src/config/default_extrinsic_params.rs new file mode 100644 index 0000000000..f044574258 --- /dev/null +++ b/subxt/src/config/default_extrinsic_params.rs @@ -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 = signed_extensions::AnyOf< + T, + ( + signed_extensions::CheckSpecVersion, + signed_extensions::CheckTxVersion, + signed_extensions::CheckNonce, + signed_extensions::CheckGenesis, + signed_extensions::CheckMortality, + 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 { + /// `None` means the tx will be immortal. + mortality: Option>, + /// `None` means we'll use the native token. + tip_of_asset_id: Option, + tip: u128, + tip_of: u128, +} + +struct Mortality { + /// 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 Default for DefaultExtrinsicParamsBuilder { + fn default() -> Self { + Self { + mortality: None, + tip: 0, + tip_of: 0, + tip_of_asset_id: None, + } + } +} + +impl DefaultExtrinsicParamsBuilder { + /// 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) -> as ExtrinsicParams>::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, + ) + } +} diff --git a/subxt/src/config/extrinsic_params.rs b/subxt/src/config/extrinsic_params.rs index 6dc16ec196..312b64a00b 100644 --- a/subxt/src/config/extrinsic_params.rs +++ b/subxt/src/config/extrinsic_params.rs @@ -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; + +impl From 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: Debug + 'static { +/// "additional" parameters that are a part of the transaction payload +/// or the signer payload respectively. +pub trait ExtrinsicParams: 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; + /// The type of error returned from [`ExtrinsicParams::new()`]. + type Error: Into; + /// Construct a new instance of our [`ExtrinsicParams`] - fn new( - spec_version: u32, - tx_version: u32, + fn new>( nonce: u64, - genesis_hash: Hash, + client: Client, other_params: Self::OtherParams, - ) -> Self; + ) -> Result; +} +/// 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); + fn encode_extra_to(&self, _v: &mut Vec) {} /// 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); -} - -/// 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 { - /// 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, -} - -/// 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 { - era: Era, - mortality_checkpoint: Option, - tip: Tip, -} - -impl BaseExtrinsicParamsBuilder { - /// 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) -> Self { - self.tip = tip.into(); - self - } -} - -impl Default for BaseExtrinsicParamsBuilder { - fn default() -> Self { - Self { - era: Era::Immortal, - mortality_checkpoint: None, - tip: Tip::default(), - } - } -} - -impl ExtrinsicParams - for BaseExtrinsicParams -{ - type OtherParams = BaseExtrinsicParamsBuilder; - - 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) { - 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) { - ( - 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(&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(input: &mut I) -> Result { - 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) {} } diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index 77fb73001e..7638ddd167 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -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 + Sync + Send + DeserializeOwned; /// This type defines the extrinsic extra and additional parameters. - type ExtrinsicParams: extrinsic_params::ExtrinsicParams; + type ExtrinsicParams: ExtrinsicParams; } +/// given some [`Config`], this return the other params needed for its `ExtrinsicParams`. +pub type OtherParamsFor = <::ExtrinsicParams as ExtrinsicParams>::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> { - _marker: std::marker::PhantomData<(T, E)>, -} - -impl> Config - for WithExtrinsicParams -{ - 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 { diff --git a/subxt/src/config/polkadot.rs b/subxt/src/config/polkadot.rs index ba33dc8ac6..5fb3390483 100644 --- a/subxt/src/config/polkadot.rs +++ b/subxt/src/config/polkadot.rs @@ -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 = BaseExtrinsicParams; +pub type PolkadotExtrinsicParams = DefaultExtrinsicParams; /// A builder which leads to [`PolkadotExtrinsicParams`] being constructed. /// This is what you provide to methods like `sign_and_submit()`. -pub type PolkadotExtrinsicParamsBuilder = BaseExtrinsicParamsBuilder; - -// 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 for PlainTip { - fn from(n: u128) -> Self { - PlainTip::new(n) - } -} +pub type PolkadotExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; diff --git a/subxt/src/config/signed_extensions.rs b/subxt/src/config/signed_extensions.rs new file mode 100644 index 0000000000..86e649af74 --- /dev/null +++ b/subxt/src/config/signed_extensions.rs @@ -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: ExtrinsicParams { + /// 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 ExtrinsicParams for CheckSpecVersion { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckSpecVersion(client.runtime_version().spec_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckSpecVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckSpecVersion { + const NAME: &'static str = "CheckSpecVersion"; +} + +/// The [`CheckNonce`] signed extension. +#[derive(Debug)] +pub struct CheckNonce(Compact); + +impl ExtrinsicParams for CheckNonce { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + nonce: u64, + _client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckNonce(Compact(nonce))) + } +} + +impl ExtrinsicParamsEncoder for CheckNonce { + fn encode_extra_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckNonce { + const NAME: &'static str = "CheckNonce"; +} + +/// The [`CheckTxVersion`] signed extension. +#[derive(Debug)] +pub struct CheckTxVersion(u32); + +impl ExtrinsicParams for CheckTxVersion { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckTxVersion(client.runtime_version().transaction_version)) + } +} + +impl ExtrinsicParamsEncoder for CheckTxVersion { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckTxVersion { + const NAME: &'static str = "CheckTxVersion"; +} + +/// The [`CheckGenesis`] signed extension. +pub struct CheckGenesis(T::Hash); + +impl std::fmt::Debug for CheckGenesis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("CheckGenesis").field(&self.0).finish() + } +} + +impl ExtrinsicParams for CheckGenesis { + type OtherParams = (); + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + _other_params: Self::OtherParams, + ) -> Result { + Ok(CheckGenesis(client.genesis_hash())) + } +} + +impl ExtrinsicParamsEncoder for CheckGenesis { + fn encode_additional_to(&self, v: &mut Vec) { + self.0.encode_to(v); + } +} + +impl SignedExtension for CheckGenesis { + const NAME: &'static str = "CheckGenesis"; +} + +/// The [`CheckMortality`] signed extension. +pub struct CheckMortality { + era: Era, + checkpoint: T::Hash, +} + +/// Parameters to configure the [`CheckMortality`] signed extension. +pub struct CheckMortalityParams { + era: Era, + checkpoint: Option, +} + +impl Default for CheckMortalityParams { + fn default() -> Self { + Self { + era: Default::default(), + checkpoint: Default::default(), + } + } +} + +impl CheckMortalityParams { + /// 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 std::fmt::Debug for CheckMortality { + 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 ExtrinsicParams for CheckMortality { + type OtherParams = CheckMortalityParams; + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(CheckMortality { + era: other_params.era, + checkpoint: other_params.checkpoint.unwrap_or(client.genesis_hash()), + }) + } +} + +impl ExtrinsicParamsEncoder for CheckMortality { + fn encode_extra_to(&self, v: &mut Vec) { + self.era.encode_to(v); + } + fn encode_additional_to(&self, v: &mut Vec) { + self.checkpoint.encode_to(v) + } +} + +impl SignedExtension for CheckMortality { + const NAME: &'static str = "CheckMortality"; +} + +/// The [`ChargeAssetTxPayment`] signed extension. +#[derive(Debug)] +pub struct ChargeAssetTxPayment { + tip: Compact, + asset_id: Option, +} + +/// Parameters to configure the [`ChargeAssetTxPayment`] signed extension. +#[derive(Default)] +pub struct ChargeAssetTxPaymentParams { + tip: u128, + asset_id: Option, +} + +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 ExtrinsicParams for ChargeAssetTxPayment { + type OtherParams = ChargeAssetTxPaymentParams; + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + _client: Client, + other_params: Self::OtherParams, + ) -> Result { + 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) { + (self.tip, self.asset_id).encode_to(v); + } +} + +impl SignedExtension for ChargeAssetTxPayment { + const NAME: &'static str = "ChargeAssetTxPayment"; +} + +/// The [`ChargeTransactionPayment`] signed extension. +#[derive(Debug)] +pub struct ChargeTransactionPayment { + tip: Compact, +} + +/// 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 ExtrinsicParams for ChargeTransactionPayment { + type OtherParams = ChargeTransactionPaymentParams; + type Error = std::convert::Infallible; + + fn new>( + _nonce: u64, + _client: Client, + other_params: Self::OtherParams, + ) -> Result { + Ok(ChargeTransactionPayment { + tip: Compact(other_params.tip), + }) + } +} + +impl ExtrinsicParamsEncoder for ChargeTransactionPayment { + fn encode_extra_to(&self, v: &mut Vec) { + self.tip.encode_to(v); + } +} + +impl SignedExtension 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 { + params: Vec>, + _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 ExtrinsicParams for AnyOf + where + T: Config, + $($ident: SignedExtension,)+ + { + type OtherParams = ($($ident::OtherParams,)+); + type Error = ExtrinsicParamsError; + + fn new>( + nonce: u64, + client: Client, + other_params: Self::OtherParams, + ) -> Result { + // First, push encoders to map as we are given them: + let mut map = HashMap::new(); + $({ + let e: Box + = 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 ExtrinsicParamsEncoder for AnyOf + where + T: Config, + $($ident: SignedExtension,)+ + { + fn encode_extra_to(&self, v: &mut Vec) { + for ext in &self.params { + ext.encode_extra_to(v); + } + } + fn encode_additional_to(&self, v: &mut Vec) { + 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, + } +} diff --git a/subxt/src/config/substrate.rs b/subxt/src/config/substrate.rs index 83b780ac3c..29bff1f03d 100644 --- a/subxt/src/config/substrate.rs +++ b/subxt/src/config/substrate.rs @@ -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 = BaseExtrinsicParams; +pub type SubstrateExtrinsicParams = DefaultExtrinsicParams; /// A builder which leads to [`SubstrateExtrinsicParams`] being constructed. /// This is what you provide to methods like `sign_and_submit()`. -pub type SubstrateExtrinsicParamsBuilder = BaseExtrinsicParamsBuilder; - -// 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, -} - -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 for AssetTip { - fn from(n: u128) -> Self { - AssetTip::new(n) - } -} +pub type SubstrateExtrinsicParamsBuilder = DefaultExtrinsicParamsBuilder; /// A type that can hash values using the blaks2_256 algorithm. #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode)] diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index f6d5687cc9..54bd80414c 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -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 for Error { } } +impl From 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)] diff --git a/subxt/src/tx/tx_client.rs b/subxt/src/tx/tx_client.rs index 3dfc096ea8..f2a538eddd 100644 --- a/subxt/src/tx/tx_client.rs +++ b/subxt/src/tx/tx_client.rs @@ -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> TxClient { &self, call: &Call, account_nonce: u64, - other_params: >::OtherParams, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -124,17 +124,12 @@ impl> TxClient { 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(); - >::new( - runtime.spec_version, - runtime.transaction_version, - account_nonce, - self.client.genesis_hash(), - other_params, - ) - }; + let additional_and_extra_params = >::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> TxClient { call: &Call, signer: &Signer, account_nonce: u64, - other_params: >::OtherParams, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -203,7 +198,7 @@ where &self, call: &Call, account_id: &T::AccountId, - other_params: >::OtherParams, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -217,7 +212,7 @@ where &self, call: &Call, signer: &Signer, - other_params: >::OtherParams, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -240,7 +235,7 @@ where where Call: TxPayload, Signer: SignerT, - >::OtherParams: Default, + >::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: >::OtherParams, + other_params: >::OtherParams, ) -> Result, Error> where Call: TxPayload, @@ -284,7 +279,7 @@ where where Call: TxPayload, Signer: SignerT, - >::OtherParams: Default, + >::OtherParams: Default, { self.sign_and_submit(call, signer, Default::default()).await } @@ -301,7 +296,7 @@ where &self, call: &Call, signer: &Signer, - other_params: >::OtherParams, + other_params: >::OtherParams, ) -> Result where Call: TxPayload, diff --git a/subxt/src/tx/tx_progress.rs b/subxt/src/tx/tx_progress.rs index 440ed10cb1..561bec2f95 100644 --- a/subxt/src/tx/tx_progress.rs +++ b/subxt/src/tx/tx_progress.rs @@ -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; - type MockHash = , - > as Config>::Hash; + type MockHash = ::Hash; type MockSubstrateTxStatus = SubstrateTxStatus; /// a mock client to satisfy trait bounds in tests @@ -441,21 +437,21 @@ mod test { impl OfflineClientT 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) -> ::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 for MockClient { fn rpc(&self) -> &crate::rpc::Rpc { - panic!("just a mock impl to satisfy trait bounds") + unimplemented!("just a mock impl to satisfy trait bounds") } } diff --git a/subxt/src/utils/era.rs b/subxt/src/utils/era.rs new file mode 100644 index 0000000000..63bede4432 --- /dev/null +++ b/subxt/src/utils/era.rs @@ -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(&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(input: &mut I) -> Result { + 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()) + } + } + } +} diff --git a/subxt/src/utils/mod.rs b/subxt/src/utils/mod.rs index a9965355cc..d216c2d761 100644 --- a/subxt/src/utils/mod.rs +++ b/subxt/src/utils/mod.rs @@ -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;