mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 10:57:56 +00:00
Support constructing and submitting V5 transactions (#1931)
* TransactionExtensions basic support for V5 VerifySignature and renames * WIP: subxt-core v5 transaction support * Subxt to support V5 extrinsics * WIP tests failing with wsm trap error * Actually encode mortality to fix tx encode issue * fmt * rename to sign_with_account_and_signature * Add explicit methods for v4 and v5 ext construction * clippy * fix wasm example and no mut self where not needed * fix doc example * another doc fix * Add tests for tx encoding and fix v5 encode issue * add copyright and todo * refactor APIs to have clear v4/v5 split in core and slightly nicer split in subxt proper * rename Partial/SubmittableExtrinsic to *Transaction * Remove SignerT::address since it's not needed * doc fixes * fmt * doc fixes * Fix comment number * Clarify panic behaviour of inject_signature * fmt
This commit is contained in:
+21
-9
@@ -12,7 +12,7 @@ use crate::utils::node_runtime;
|
||||
#[cfg(fullclient)]
|
||||
use subxt::{
|
||||
config::{
|
||||
signed_extensions::{ChargeAssetTxPayment, CheckMortality, CheckNonce},
|
||||
transaction_extensions::{ChargeAssetTxPayment, CheckMortality, CheckNonce},
|
||||
DefaultExtrinsicParamsBuilder, SubstrateConfig,
|
||||
},
|
||||
utils::Era,
|
||||
@@ -262,7 +262,7 @@ async fn fetch_block_and_decode_extrinsic_details() {
|
||||
|
||||
#[cfg(fullclient)]
|
||||
#[subxt_test]
|
||||
async fn decode_signed_extensions_from_blocks() {
|
||||
async fn decode_transaction_extensions_from_blocks() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let alice = dev::alice();
|
||||
@@ -301,7 +301,7 @@ async fn decode_signed_extensions_from_blocks() {
|
||||
}
|
||||
|
||||
let transaction1 = submit_transfer_extrinsic_and_get_it_back!(1234);
|
||||
let extensions1 = transaction1.signed_extensions().unwrap();
|
||||
let extensions1 = transaction1.transaction_extensions().unwrap();
|
||||
|
||||
let nonce1 = extensions1.nonce().unwrap();
|
||||
let nonce1_static = extensions1.find::<CheckNonce>().unwrap().unwrap();
|
||||
@@ -313,7 +313,7 @@ async fn decode_signed_extensions_from_blocks() {
|
||||
.tip();
|
||||
|
||||
let transaction2 = submit_transfer_extrinsic_and_get_it_back!(5678);
|
||||
let extensions2 = transaction2.signed_extensions().unwrap();
|
||||
let extensions2 = transaction2.transaction_extensions().unwrap();
|
||||
let nonce2 = extensions2.nonce().unwrap();
|
||||
let nonce2_static = extensions2.find::<CheckNonce>().unwrap().unwrap();
|
||||
let tip2 = extensions2.tip().unwrap();
|
||||
@@ -332,7 +332,7 @@ async fn decode_signed_extensions_from_blocks() {
|
||||
assert_eq!(tip2, 5678);
|
||||
assert_eq!(tip2, tip2_static);
|
||||
|
||||
let expected_signed_extensions = [
|
||||
let expected_transaction_extensions = [
|
||||
"CheckNonZeroSender",
|
||||
"CheckSpecVersion",
|
||||
"CheckTxVersion",
|
||||
@@ -345,13 +345,25 @@ async fn decode_signed_extensions_from_blocks() {
|
||||
"WeightReclaim",
|
||||
];
|
||||
|
||||
assert_eq!(extensions1.iter().count(), expected_signed_extensions.len());
|
||||
for (e, expected_name) in extensions1.iter().zip(expected_signed_extensions.iter()) {
|
||||
assert_eq!(
|
||||
extensions1.iter().count(),
|
||||
expected_transaction_extensions.len()
|
||||
);
|
||||
for (e, expected_name) in extensions1
|
||||
.iter()
|
||||
.zip(expected_transaction_extensions.iter())
|
||||
{
|
||||
assert_eq!(e.name(), *expected_name);
|
||||
}
|
||||
|
||||
assert_eq!(extensions2.iter().count(), expected_signed_extensions.len());
|
||||
for (e, expected_name) in extensions2.iter().zip(expected_signed_extensions.iter()) {
|
||||
assert_eq!(
|
||||
extensions2.iter().count(),
|
||||
expected_transaction_extensions.len()
|
||||
);
|
||||
for (e, expected_name) in extensions2
|
||||
.iter()
|
||||
.zip(expected_transaction_extensions.iter())
|
||||
{
|
||||
assert_eq!(e.name(), *expected_name);
|
||||
}
|
||||
|
||||
@@ -271,8 +271,9 @@ async fn transactionwatch_v1_submit_and_watch() {
|
||||
let tx_bytes = ctx
|
||||
.client()
|
||||
.tx()
|
||||
.create_signed_offline(&payload, &dev::alice(), Default::default())
|
||||
.create_partial_offline(&payload, Default::default())
|
||||
.unwrap()
|
||||
.sign(&dev::alice())
|
||||
.into_encoded();
|
||||
|
||||
// Test submitting it:
|
||||
@@ -337,8 +338,9 @@ async fn transaction_v1_broadcast() {
|
||||
let tx = ctx
|
||||
.client()
|
||||
.tx()
|
||||
.create_signed_offline(&tx_payload, &dev::alice(), Default::default())
|
||||
.unwrap();
|
||||
.create_partial_offline(&tx_payload, Default::default())
|
||||
.unwrap()
|
||||
.sign(&dev::alice());
|
||||
|
||||
let tx_hash = tx.hash();
|
||||
let tx_bytes = tx.into_encoded();
|
||||
@@ -407,8 +409,9 @@ async fn transaction_v1_stop() {
|
||||
let tx_bytes = ctx
|
||||
.client()
|
||||
.tx()
|
||||
.create_signed_offline(&tx, &dev::alice(), Default::default())
|
||||
.create_partial_offline(&tx, Default::default())
|
||||
.unwrap()
|
||||
.sign(&dev::alice())
|
||||
.into_encoded();
|
||||
|
||||
// Submit the transaction.
|
||||
|
||||
@@ -190,9 +190,9 @@ async fn external_signing() {
|
||||
// Create a partial extrinsic. We can get the signer payload at this point, to be
|
||||
// signed externally.
|
||||
let tx = node_runtime::tx().preimage().note_preimage(vec![0u8]);
|
||||
let partial_extrinsic = api
|
||||
let mut partial_extrinsic = api
|
||||
.tx()
|
||||
.create_partial_signed(&tx, &alice.public_key().into(), Default::default())
|
||||
.create_partial(&tx, &alice.public_key().into(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -202,7 +202,7 @@ async fn external_signing() {
|
||||
let signature = alice.sign(&signer_payload);
|
||||
// Use this to build a signed extrinsic.
|
||||
let extrinsic = partial_extrinsic
|
||||
.sign_with_address_and_signature(&alice.public_key().into(), &signature.into());
|
||||
.sign_with_account_and_signature(&alice.public_key().into(), &signature.into());
|
||||
|
||||
// And now submit it.
|
||||
extrinsic
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
mod validation;
|
||||
@@ -6,6 +6,7 @@ mod blocks;
|
||||
mod client;
|
||||
mod codegen;
|
||||
mod frame;
|
||||
mod metadata;
|
||||
mod metadata_validation;
|
||||
mod runtime_api;
|
||||
mod storage;
|
||||
mod transactions;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::utils::node_runtime;
|
||||
use crate::{subxt_test, test_context};
|
||||
use core::ops::Deref;
|
||||
use frame_decode::extrinsics::ExtrinsicType;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
// TODO: When VerifySignature exists on the substrate kitchensink runtime,
|
||||
// let's try actuallty submitting v4 and v5 signed extrinsics to verify that
|
||||
// they are actually accepted by the node.
|
||||
|
||||
#[subxt_test]
|
||||
async fn v4_unsigned_encode_decode() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let md = api.metadata();
|
||||
|
||||
let call = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer_allow_death(dev::bob().public_key().into(), 1000);
|
||||
|
||||
let tx_bytes = api.tx().create_v4_unsigned(&call).unwrap().into_encoded();
|
||||
let tx_bytes_cursor = &mut &*tx_bytes;
|
||||
|
||||
let decoded = frame_decode::extrinsics::decode_extrinsic(
|
||||
tx_bytes_cursor,
|
||||
md.deref(),
|
||||
api.metadata().types(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tx_bytes_cursor.len(), 0);
|
||||
assert_eq!(decoded.version(), 4);
|
||||
assert_eq!(decoded.ty(), ExtrinsicType::Bare);
|
||||
assert_eq!(decoded.pallet_name(), "Balances");
|
||||
assert_eq!(decoded.call_name(), "transfer_allow_death");
|
||||
assert!(decoded.signature_payload().is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
async fn v5_bare_encode_decode() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let md = api.metadata();
|
||||
|
||||
let call = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer_allow_death(dev::bob().public_key().into(), 1000);
|
||||
|
||||
let tx_bytes = api.tx().create_v5_bare(&call).unwrap().into_encoded();
|
||||
let tx_bytes_cursor = &mut &*tx_bytes;
|
||||
|
||||
let decoded = frame_decode::extrinsics::decode_extrinsic(
|
||||
tx_bytes_cursor,
|
||||
md.deref(),
|
||||
api.metadata().types(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tx_bytes_cursor.len(), 0);
|
||||
assert_eq!(decoded.version(), 5);
|
||||
assert_eq!(decoded.ty(), ExtrinsicType::Bare);
|
||||
assert_eq!(decoded.pallet_name(), "Balances");
|
||||
assert_eq!(decoded.call_name(), "transfer_allow_death");
|
||||
assert!(decoded.transaction_extension_payload().is_none());
|
||||
assert!(decoded.signature_payload().is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
async fn v4_signed_encode_decode() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let md = api.metadata();
|
||||
|
||||
let call = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer_allow_death(dev::bob().public_key().into(), 1000);
|
||||
|
||||
let tx_bytes = api
|
||||
.tx()
|
||||
.create_v4_partial(&call, &dev::alice().public_key().into(), Default::default())
|
||||
.await
|
||||
.unwrap()
|
||||
.sign(&dev::alice())
|
||||
.into_encoded();
|
||||
let tx_bytes_cursor = &mut &*tx_bytes;
|
||||
|
||||
let decoded = frame_decode::extrinsics::decode_extrinsic(
|
||||
tx_bytes_cursor,
|
||||
md.deref(),
|
||||
api.metadata().types(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tx_bytes_cursor.len(), 0);
|
||||
assert_eq!(decoded.version(), 4);
|
||||
assert_eq!(decoded.ty(), ExtrinsicType::Signed);
|
||||
assert_eq!(decoded.pallet_name(), "Balances");
|
||||
assert_eq!(decoded.call_name(), "transfer_allow_death");
|
||||
assert!(decoded.signature_payload().is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[subxt_test]
|
||||
async fn v5_general_encode_decode() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let md = api.metadata();
|
||||
let dummy_signer = dev::alice();
|
||||
|
||||
let call = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer_allow_death(dev::bob().public_key().into(), 1000);
|
||||
|
||||
let tx_bytes = api
|
||||
.tx()
|
||||
.create_v5_partial(&call, &dev::alice().public_key().into(), Default::default())
|
||||
.await
|
||||
.unwrap()
|
||||
.sign(&dummy_signer) // No signature payload is added, but may be inserted into tx extensions.
|
||||
.into_encoded();
|
||||
let tx_bytes_cursor = &mut &*tx_bytes;
|
||||
|
||||
let decoded = frame_decode::extrinsics::decode_extrinsic(
|
||||
tx_bytes_cursor,
|
||||
md.deref(),
|
||||
api.metadata().types(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tx_bytes_cursor.len(), 0);
|
||||
assert_eq!(decoded.version(), 5);
|
||||
assert_eq!(decoded.ty(), ExtrinsicType::General);
|
||||
assert_eq!(decoded.pallet_name(), "Balances");
|
||||
assert_eq!(decoded.call_name(), "transfer_allow_death");
|
||||
assert!(decoded.transaction_extension_payload().is_some());
|
||||
// v5 general extrinsics have no signature payload; signature in tx extensions:
|
||||
assert!(decoded.signature_payload().is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user