subxt_core/tx/mod.rs
1// Copyright 2019-2024 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! Construct and sign transactions.
6//!
7//! # Example
8//!
9//! ```rust
10//! use subxt_signer::sr25519::dev;
11//! use subxt_macro::subxt;
12//! use subxt_core::config::{PolkadotConfig, HashFor};
13//! use subxt_core::config::DefaultExtrinsicParamsBuilder as Params;
14//! use subxt_core::tx;
15//! use subxt_core::utils::H256;
16//! use subxt_core::metadata;
17//!
18//! // If we generate types without `subxt`, we need to point to `::subxt_core`:
19//! #[subxt(
20//! crate = "::subxt_core",
21//! runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale",
22//! )]
23//! pub mod polkadot {}
24//!
25//! // Gather some other information about the chain that we'll need to construct valid extrinsics:
26//! let state = tx::ClientState::<PolkadotConfig> {
27//! metadata: {
28//! let metadata_bytes = include_bytes!("../../../artifacts/polkadot_metadata_small.scale");
29//! metadata::decode_from(&metadata_bytes[..]).unwrap()
30//! },
31//! genesis_hash: {
32//! let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
33//! let bytes = hex::decode(h).unwrap();
34//! H256::from_slice(&bytes)
35//! },
36//! runtime_version: tx::RuntimeVersion {
37//! spec_version: 9370,
38//! transaction_version: 20,
39//! }
40//! };
41//!
42//! // Now we can build a balance transfer extrinsic.
43//! let dest = dev::bob().public_key().into();
44//! let call = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
45//! let params = Params::new().tip(1_000).nonce(0).build();
46//!
47//! // We can validate that this lines up with the given metadata:
48//! tx::validate(&call, &state.metadata).unwrap();
49//!
50//! // We can build a signed transaction:
51//! let signed_call = tx::create_v4_signed(&call, &state, params)
52//! .unwrap()
53//! .sign(&dev::alice());
54//!
55//! // And log it:
56//! println!("Tx: 0x{}", hex::encode(signed_call.encoded()));
57//! ```
58
59pub mod payload;
60pub mod signer;
61
62use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
63use crate::error::{Error, ExtrinsicError, MetadataError};
64use crate::metadata::Metadata;
65use crate::utils::Encoded;
66use alloc::borrow::{Cow, ToOwned};
67use alloc::vec::Vec;
68use codec::{Compact, Encode};
69use payload::Payload;
70use signer::Signer as SignerT;
71use sp_crypto_hashing::blake2_256;
72
73// Expose these here since we expect them in some calls below.
74pub use crate::client::{ClientState, RuntimeVersion};
75
76/// Run the validation logic against some extrinsic you'd like to submit. Returns `Ok(())`
77/// if the call is valid (or if it's not possible to check since the call has no validation hash).
78/// Return an error if the call was not valid or something went wrong trying to validate it (ie
79/// the pallet or call in question do not exist at all).
80pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
81 if let Some(details) = call.validation_details() {
82 let expected_hash = metadata
83 .pallet_by_name_err(details.pallet_name)?
84 .call_hash(details.call_name)
85 .ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
86
87 if details.hash != expected_hash {
88 return Err(MetadataError::IncompatibleCodegen.into());
89 }
90 }
91 Ok(())
92}
93
94/// Returns the suggested transaction versions to build for a given chain, or an error
95/// if Subxt doesn't support any version expected by the chain.
96///
97/// If the result is [`TransactionVersion::V4`], use the `v4` methods in this module. If it's
98/// [`TransactionVersion::V5`], use the `v5` ones.
99pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Error> {
100 let versions = metadata.extrinsic().supported_versions();
101
102 if versions.contains(&4) {
103 Ok(TransactionVersion::V4)
104 } else if versions.contains(&5) {
105 Ok(TransactionVersion::V5)
106 } else {
107 Err(ExtrinsicError::UnsupportedVersion.into())
108 }
109}
110
111/// The transaction versions supported by Subxt.
112#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
113pub enum TransactionVersion {
114 /// v4 transactions (signed and unsigned transactions)
115 V4,
116 /// v5 transactions (bare and general transactions)
117 V5,
118}
119
120/// Return the SCALE encoded bytes representing the call data of the transaction.
121pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
122 let mut bytes = Vec::new();
123 call.encode_call_data_to(metadata, &mut bytes)?;
124 Ok(bytes)
125}
126
127/// Creates a V4 "unsigned" transaction without submitting it.
128pub fn create_v4_unsigned<T: Config, Call: Payload>(
129 call: &Call,
130 metadata: &Metadata,
131) -> Result<Transaction<T>, Error> {
132 create_unsigned_at_version(call, 4, metadata)
133}
134
135/// Creates a V5 "bare" transaction without submitting it.
136pub fn create_v5_bare<T: Config, Call: Payload>(
137 call: &Call,
138 metadata: &Metadata,
139) -> Result<Transaction<T>, Error> {
140 create_unsigned_at_version(call, 5, metadata)
141}
142
143// Create a V4 "unsigned" transaction or V5 "bare" transaction.
144fn create_unsigned_at_version<T: Config, Call: Payload>(
145 call: &Call,
146 tx_version: u8,
147 metadata: &Metadata,
148) -> Result<Transaction<T>, Error> {
149 // 1. Validate this call against the current node metadata if the call comes
150 // with a hash allowing us to do so.
151 validate(call, metadata)?;
152
153 // 2. Encode extrinsic
154 let extrinsic = {
155 let mut encoded_inner = Vec::new();
156 // encode the transaction version first.
157 tx_version.encode_to(&mut encoded_inner);
158 // encode call data after this byte.
159 call.encode_call_data_to(metadata, &mut encoded_inner)?;
160 // now, prefix byte length:
161 let len = Compact(
162 u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
163 );
164 let mut encoded = Vec::new();
165 len.encode_to(&mut encoded);
166 encoded.extend(encoded_inner);
167 encoded
168 };
169
170 // Wrap in Encoded to ensure that any more "encode" calls leave it in the right state.
171 Ok(Transaction::from_bytes(extrinsic))
172}
173
174/// Construct a v4 extrinsic, ready to be signed.
175pub fn create_v4_signed<T: Config, Call: Payload>(
176 call: &Call,
177 client_state: &ClientState<T>,
178 params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
179) -> Result<PartialTransactionV4<T>, Error> {
180 // 1. Validate this call against the current node metadata if the call comes
181 // with a hash allowing us to do so.
182 validate(call, &client_state.metadata)?;
183
184 // 2. SCALE encode call data to bytes (pallet u8, call u8, call params).
185 let call_data = call_data(call, &client_state.metadata)?;
186
187 // 3. Construct our custom additional/extra params.
188 let additional_and_extra_params =
189 <T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
190
191 // Return these details, ready to construct a signed extrinsic from.
192 Ok(PartialTransactionV4 {
193 call_data,
194 additional_and_extra_params,
195 })
196}
197
198/// Construct a v5 "general" extrinsic, ready to be signed or emitted as is.
199pub fn create_v5_general<T: Config, Call: Payload>(
200 call: &Call,
201 client_state: &ClientState<T>,
202 params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
203) -> Result<PartialTransactionV5<T>, Error> {
204 // 1. Validate this call against the current node metadata if the call comes
205 // with a hash allowing us to do so.
206 validate(call, &client_state.metadata)?;
207
208 // 2. Work out which TX extension version to target based on metadata.
209 let tx_extensions_version = client_state
210 .metadata
211 .extrinsic()
212 .transaction_extension_version_to_use_for_encoding();
213
214 // 3. SCALE encode call data to bytes (pallet u8, call u8, call params).
215 let call_data = call_data(call, &client_state.metadata)?;
216
217 // 4. Construct our custom additional/extra params.
218 let additional_and_extra_params =
219 <T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
220
221 // Return these details, ready to construct a signed extrinsic from.
222 Ok(PartialTransactionV5 {
223 call_data,
224 additional_and_extra_params,
225 tx_extensions_version,
226 })
227}
228
229/// A partially constructed V4 extrinsic, ready to be signed.
230pub struct PartialTransactionV4<T: Config> {
231 call_data: Vec<u8>,
232 additional_and_extra_params: T::ExtrinsicParams,
233}
234
235impl<T: Config> PartialTransactionV4<T> {
236 /// Return the bytes representing the call data for this partially constructed
237 /// extrinsic.
238 pub fn call_data(&self) -> &[u8] {
239 &self.call_data
240 }
241
242 // Obtain bytes representing the signer payload and run call some function
243 // with them. This can avoid an allocation in some cases.
244 fn with_signer_payload<F, R>(&self, f: F) -> R
245 where
246 F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
247 {
248 let mut bytes = self.call_data.clone();
249 self.additional_and_extra_params
250 .encode_signer_payload_value_to(&mut bytes);
251 self.additional_and_extra_params
252 .encode_implicit_to(&mut bytes);
253
254 if bytes.len() > 256 {
255 f(Cow::Borrowed(&blake2_256(&bytes)))
256 } else {
257 f(Cow::Owned(bytes))
258 }
259 }
260
261 /// Return the V4 signer payload for this extrinsic. These are the bytes that must
262 /// be signed in order to produce a valid signature for the extrinsic.
263 pub fn signer_payload(&self) -> Vec<u8> {
264 self.with_signer_payload(|bytes| bytes.to_vec())
265 }
266
267 /// Convert this [`PartialTransactionV4`] into a V4 signed [`Transaction`], ready to submit.
268 /// The provided `signer` is responsible for providing the "from" address for the transaction,
269 /// as well as providing a signature to attach to it.
270 pub fn sign<Signer>(&self, signer: &Signer) -> Transaction<T>
271 where
272 Signer: SignerT<T>,
273 {
274 // Given our signer, we can sign the payload representing this extrinsic.
275 let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
276 // Now, use the signature and "from" address to build the extrinsic.
277 self.sign_with_account_and_signature(signer.account_id(), &signature)
278 }
279
280 /// Convert this [`PartialTransactionV4`] into a V4 signed [`Transaction`], ready to submit.
281 /// The provided `address` and `signature` will be used.
282 pub fn sign_with_account_and_signature(
283 &self,
284 account_id: T::AccountId,
285 signature: &T::Signature,
286 ) -> Transaction<T> {
287 let extrinsic = {
288 let mut encoded_inner = Vec::new();
289 // "is signed" + transaction protocol version (4)
290 (0b10000000 + 4u8).encode_to(&mut encoded_inner);
291 // from address for signature
292 let address: T::Address = account_id.into();
293 address.encode_to(&mut encoded_inner);
294 // the signature
295 signature.encode_to(&mut encoded_inner);
296 // attach custom extra params
297 self.additional_and_extra_params
298 .encode_value_to(&mut encoded_inner);
299 // and now, call data (remembering that it's been encoded already and just needs appending)
300 encoded_inner.extend(&self.call_data);
301 // now, prefix byte length:
302 let len = Compact(
303 u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
304 );
305 let mut encoded = Vec::new();
306 len.encode_to(&mut encoded);
307 encoded.extend(encoded_inner);
308 encoded
309 };
310
311 // Return an extrinsic ready to be submitted.
312 Transaction::from_bytes(extrinsic)
313 }
314}
315
316/// A partially constructed V5 general extrinsic, ready to be signed or emitted as-is.
317pub struct PartialTransactionV5<T: Config> {
318 call_data: Vec<u8>,
319 additional_and_extra_params: T::ExtrinsicParams,
320 tx_extensions_version: u8,
321}
322
323impl<T: Config> PartialTransactionV5<T> {
324 /// Return the bytes representing the call data for this partially constructed
325 /// extrinsic.
326 pub fn call_data(&self) -> &[u8] {
327 &self.call_data
328 }
329
330 /// Return the V5 signer payload for this extrinsic. These are the bytes that must
331 /// be signed in order to produce a valid signature for the extrinsic.
332 pub fn signer_payload(&self) -> [u8; 32] {
333 let mut bytes = self.call_data.clone();
334
335 self.additional_and_extra_params
336 .encode_signer_payload_value_to(&mut bytes);
337 self.additional_and_extra_params
338 .encode_implicit_to(&mut bytes);
339
340 blake2_256(&bytes)
341 }
342
343 /// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`].
344 ///
345 /// This transaction has not been explicitly signed. Use [`Self::sign`]
346 /// or [`Self::sign_with_account_and_signature`] if you wish to provide a
347 /// signature (this is usually a necessary step).
348 pub fn to_transaction(&self) -> Transaction<T> {
349 let extrinsic = {
350 let mut encoded_inner = Vec::new();
351 // "is general" + transaction protocol version (5)
352 (0b01000000 + 5u8).encode_to(&mut encoded_inner);
353 // Encode versions for the transaction extensions
354 self.tx_extensions_version.encode_to(&mut encoded_inner);
355 // Encode the actual transaction extensions values
356 self.additional_and_extra_params
357 .encode_value_to(&mut encoded_inner);
358 // and now, call data (remembering that it's been encoded already and just needs appending)
359 encoded_inner.extend(&self.call_data);
360 // now, prefix byte length:
361 let len = Compact(
362 u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
363 );
364 let mut encoded = Vec::new();
365 len.encode_to(&mut encoded);
366 encoded.extend(encoded_inner);
367 encoded
368 };
369
370 // Return an extrinsic ready to be submitted.
371 Transaction::from_bytes(extrinsic)
372 }
373
374 /// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`] with a signature.
375 ///
376 /// Signing the transaction injects the signature into the transaction extension data, which is why
377 /// this method borrows self mutably. Signing repeatedly will override the previous signature.
378 pub fn sign<Signer>(&mut self, signer: &Signer) -> Transaction<T>
379 where
380 Signer: SignerT<T>,
381 {
382 // Given our signer, we can sign the payload representing this extrinsic.
383 let signature = signer.sign(&self.signer_payload());
384 // Now, use the signature and "from" account to build the extrinsic.
385 self.sign_with_account_and_signature(&signer.account_id(), &signature)
386 }
387
388 /// Convert this [`PartialTransactionV5`] into a V5 "general" [`Transaction`] with a signature.
389 /// Prefer [`Self::sign`] if you have a [`SignerT`] instance to use.
390 ///
391 /// Signing the transaction injects the signature into the transaction extension data, which is why
392 /// this method borrows self mutably. Signing repeatedly will override the previous signature.
393 pub fn sign_with_account_and_signature(
394 &mut self,
395 account_id: &T::AccountId,
396 signature: &T::Signature,
397 ) -> Transaction<T> {
398 // Inject the signature into the transaction extensions
399 // before constructing it.
400 self.additional_and_extra_params
401 .inject_signature(account_id, signature);
402
403 self.to_transaction()
404 }
405}
406
407/// This represents a signed transaction that's ready to be submitted.
408/// Use [`Transaction::encoded()`] or [`Transaction::into_encoded()`] to
409/// get the bytes for it, or [`Transaction::hash_with()`] to hash the transaction
410/// given an instance of [`Config::Hasher`].
411pub struct Transaction<T> {
412 encoded: Encoded,
413 marker: core::marker::PhantomData<T>,
414}
415
416impl<T: Config> Transaction<T> {
417 /// Create a [`Transaction`] from some already-signed and prepared
418 /// extrinsic bytes,
419 pub fn from_bytes(tx_bytes: Vec<u8>) -> Self {
420 Self {
421 encoded: Encoded(tx_bytes),
422 marker: core::marker::PhantomData,
423 }
424 }
425
426 /// Calculate and return the hash of the extrinsic, based on the provided hasher.
427 /// If you don't have a hasher to hand, you can construct one using the metadata
428 /// with `T::Hasher::new(&metadata)`. This will create a hasher suitable for the
429 /// current chain where possible.
430 pub fn hash_with(&self, hasher: T::Hasher) -> HashFor<T> {
431 hasher.hash_of(&self.encoded)
432 }
433
434 /// Returns the SCALE encoded extrinsic bytes.
435 pub fn encoded(&self) -> &[u8] {
436 &self.encoded.0
437 }
438
439 /// Consumes this [`Transaction`] and returns the SCALE encoded
440 /// extrinsic bytes.
441 pub fn into_encoded(self) -> Vec<u8> {
442 self.encoded.0
443 }
444}