Files
pezkuwi-subxt/testing/integration-tests/src/full_client/blocks.rs
T
James Wilson 8203679cbd Merge v0.50.x to master (#2127)
* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)

* WIP integrating new frame-decode and working out new storage APIS

* WIP: first pass adding new storage things to subxt-core

* Second pass over Address type and start impl in Subxt

* WIP new storage APIs

* WIP New storage APIs roughly completed, lots of errors still

* Remove PlainorMap enum; plain and map values now use same struct to simplify usage

* Begin 'fixing' errors

* WIP splitting errors and tidying payload/address traits

* Get subxt-core compiling

* Small fixes in subxt-core and remove metadata mod

* subxt-core: cargo check --all-targets passes

* Fix test

* WIP starting to update subxt from subxt-core changes

* WIP splitting up subxt errors into smaller variants

* WIP errors: add DispatchError errors

* Port new Storage APIs to subxt-core

* cargo check -p subxt passes

* Quick-fix errors in subxt-cli (explore subcommand)

* fmt

* Finish fixing codegen up and start fixing examples

* get Subxt examples compiling and bytes_at for constants

* Add some arcs to limit lifetimes in subxt/subxt-core storage APIs

* A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt

* Update codegen test

* cargo check --all-targets passing

* cargo check --features 'unstable-light-client' passing

* clippy

* Remove unused dep in subxt

* use published frame-decode

* fix wasm-example

* Add new tx extension to fix daily tests

* Remove unused subxt_core::dynamic::DecodedValue type

* Update book to match changes

* Update docs to fix more broken bits

* Add missing docs

* fmt

* allow larger result errs for now

* Add missing alloc imports in subxt-core

* Fix doc tests and fix bug getting constant info

* Fix V14 -> Metadata transform for storage & constants

* Fix parachain example

* Fix FFI example

* BlockLength decodes t ostruct, not u128

* use fetch/iter shorthands rather than entry in most storage tests

* Fix some integration tests

* Fix Runtime codegen tests

* Expose the dynamic custom_value selecter and use in a UI test

* Update codegen metadata

* Tidy CLI storage query and support (str,str) as a storage address

* Add (str,str) as valid constant address too

* Show string tuple in constants example

* Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them

* clippy

* [v0.50] update scale-info-legacy and frame-decode to latest (#2119)

* bump scale-info-legacy and frame-decode to latest

* Remove something we don't need in this PR

* Fully remove unused for now dep

* [v0.50] Convert historic metadata to subxt::Metadata (#2120)

* First pass converting historic metadatas to our subxt::Metadata type

* use published frame-decode

* fmt and rename legacy metadata macro

* Enable legacy feature where needed in subxt_metadata so it compiles on its own

* Use cargo hack more in CI and fix subxt-metadata features

* Add tests for metadata conversion (need to optimise; some too expensive right now

* Address performance and equality issues in metadata conversion testing

* fmt

* fmt all

* clippy

* Fix a doc link

* Test codegen and fixes to make it work

* Remove local frame-decode patch

* bump frame-decode to latest

* [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124)

* Allow visiting extrinsic fields

* fmt

* Don't use local scale-decode dep

* Clippy and tidy

* Extend 'subxt codegen' CLI to work with legacy metadatas

* Simplify historic extrinsics example now that AccountId32s have paths/names

* clippy

* clippy

* clippy..

* Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime

* Try to fix flaky test

* Add custom value decode to extrinsics example

* Remove useless else branch ra thought I needed

* Simplify examples

* Prep to release v0.0.5 (#2126)
2025-11-22 10:44:03 +00:00

445 lines
13 KiB
Rust

// 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::{subxt_test, test_context, utils::consume_initial_blocks};
use codec::{Compact, Decode, Encode};
use futures::StreamExt;
#[cfg(fullclient)]
use crate::utils::node_runtime;
#[cfg(fullclient)]
use subxt::{
config::{
DefaultExtrinsicParamsBuilder, SubstrateConfig,
transaction_extensions::{ChargeAssetTxPayment, CheckMortality, CheckNonce},
},
utils::Era,
};
#[cfg(fullclient)]
use subxt_signer::sr25519::dev;
#[cfg(fullclient)]
#[subxt_test]
async fn block_subscriptions_are_consistent_with_eachother() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut all_sub = api.blocks().subscribe_all().await?;
let mut best_sub = api.blocks().subscribe_best().await?;
let mut finalized_sub = api.blocks().subscribe_finalized().await?;
let mut finals = vec![];
let mut bests = vec![];
let mut alls = vec![];
// Finalization can run behind a bit; blocks that were reported a while ago can
// only just now be being finalized (in the new RPCs this isn't true and we'll be
// told about all of those blocks up front). So, first we wait until finalization reports
// a block that we've seen as new.
loop {
tokio::select! {biased;
Some(Ok(b)) = all_sub.next() => alls.push(b.hash()),
Some(Ok(b)) = best_sub.next() => bests.push(b.hash()),
Some(Ok(b)) = finalized_sub.next() => if alls.contains(&b.hash()) { break },
}
}
// Now, gather a couple more finalized blocks as well as anything else we hear about.
while finals.len() < 2 {
tokio::select! {biased;
Some(Ok(b)) = all_sub.next() => alls.push(b.hash()),
Some(Ok(b)) = best_sub.next() => bests.push(b.hash()),
Some(Ok(b)) = finalized_sub.next() => finals.push(b.hash()),
}
}
// Check that the items in the first slice are found in the same order in the second slice.
fn are_same_order_in<T: PartialEq>(a_items: &[T], b_items: &[T]) -> bool {
let mut b_idx = 0;
for a in a_items {
if let Some((idx, _)) = b_items[b_idx..]
.iter()
.enumerate()
.find(|(_idx, b)| a == *b)
{
b_idx += idx;
} else {
return false;
}
}
true
}
// Final blocks and best blocks should both be subsets of _all_ of the blocks reported.
assert!(
are_same_order_in(&bests, &alls),
"Best set {bests:?} should be a subset of all: {alls:?}"
);
assert!(
are_same_order_in(&finals, &alls),
"Final set {finals:?} should be a subset of all: {alls:?}"
);
Ok(())
}
#[subxt_test]
async fn finalized_headers_subscription() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let mut sub = api.blocks().subscribe_finalized().await?;
consume_initial_blocks(&mut sub).await;
// check that the finalized block reported lines up with the `latest_finalized_block_ref`.
for _ in 0..2 {
let header = sub.next().await.unwrap()?;
let finalized_hash = api.backend().latest_finalized_block_ref().await?.hash();
assert_eq!(header.hash(), finalized_hash);
}
Ok(())
}
#[subxt_test]
async fn missing_block_headers_will_be_filled_in() -> Result<(), subxt::Error> {
use subxt::backend::legacy;
let ctx = test_context().await;
let rpc = ctx.legacy_rpc_methods().await;
// Manually subscribe to the next 6 finalized block headers, but deliberately
// filter out some in the middle so we get back b _ _ b _ b. This guarantees
// that there will be some gaps, even if there aren't any from the subscription.
let some_finalized_blocks = rpc
.chain_subscribe_finalized_heads()
.await?
.enumerate()
.take(6)
.filter(|(n, _)| {
let n = *n;
async move { n == 0 || n == 3 || n == 5 }
})
.map(|(_, r)| r);
// This should spot any gaps in the middle and fill them back in.
let all_finalized_blocks =
legacy::subscribe_to_block_headers_filling_in_gaps(rpc, some_finalized_blocks, None);
futures::pin_mut!(all_finalized_blocks);
// Iterate the block headers, making sure we get them all in order.
let mut last_block_number = None;
while let Some(header) = all_finalized_blocks.next().await {
let header = header?;
use subxt::config::Header;
let block_number: u128 = header.number().into();
if let Some(last) = last_block_number {
assert_eq!(last + 1, block_number);
}
last_block_number = Some(block_number);
}
assert!(last_block_number.is_some());
Ok(())
}
// Check that we can subscribe to non-finalized blocks.
#[subxt_test]
async fn runtime_api_call() -> Result<(), subxt::Error> {
let ctx = test_context().await;
let api = ctx.client();
let rpc = ctx.legacy_rpc_methods().await;
let mut sub = api.blocks().subscribe_best().await?;
let block = sub.next().await.unwrap()?;
let rt = block.runtime_api().await;
// get metadata via raw state_call.
let meta_bytes = rt.call_raw("Metadata_metadata", None).await?;
let (_, meta1): (Compact<u32>, frame_metadata::RuntimeMetadataPrefixed) =
Decode::decode(&mut &*meta_bytes)?;
// get metadata via `state_getMetadata`.
let meta2_bytes = rpc.state_get_metadata(Some(block.hash())).await?.into_raw();
// They should be the same.
assert_eq!(meta1.encode(), meta2_bytes);
Ok(())
}
#[cfg(fullclient)]
#[subxt_test]
async fn fetch_block_and_decode_extrinsic_details() {
let ctx = test_context().await;
let api = ctx.client();
let alice = dev::alice();
let bob = dev::bob();
// Setup; put an extrinsic into a block:
let tx = node_runtime::tx()
.balances()
.transfer_allow_death(bob.public_key().into(), 10_000);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, Default::default())
.await
.unwrap();
let in_block = signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized()
.await
.unwrap();
// Now, separately, download that block. Let's see what it contains..
let block_hash = in_block.block_hash();
let block = api.blocks().at(block_hash).await.unwrap();
// Ensure that we can clone the block.
let _ = block.clone();
let extrinsics = block.extrinsics().await.unwrap();
assert_eq!(extrinsics.block_hash(), block_hash);
// `.has` should work and find a transfer call.
assert!(
extrinsics
.has::<node_runtime::balances::calls::types::TransferAllowDeath>()
.unwrap()
);
// `.find_first` should similarly work to find the transfer call:
assert!(
extrinsics
.find_first::<node_runtime::balances::calls::types::TransferAllowDeath>()
.unwrap()
.is_some()
);
let block_extrinsics = extrinsics.iter().collect::<Vec<_>>();
let mut balance = None;
let mut timestamp = None;
for tx in block_extrinsics {
tx.as_root_extrinsic::<node_runtime::Call>().unwrap();
if let Some(ext) = tx
.as_extrinsic::<node_runtime::timestamp::calls::types::Set>()
.unwrap()
{
timestamp = Some((ext, tx.is_signed()));
}
if let Some(ext) = tx
.as_extrinsic::<node_runtime::balances::calls::types::TransferAllowDeath>()
.unwrap()
{
balance = Some((ext, tx.is_signed()));
}
}
// Check that we found the timestamp
{
let (_, is_signed) = timestamp.expect("Timestamp not found");
assert!(!is_signed);
}
// Check that we found the balance transfer
{
let (tx, is_signed) = balance.expect("Balance transfer not found");
assert_eq!(tx.value, 10_000);
assert!(is_signed);
}
}
/// A helper function to submit a transaction with some params and then get it back in a block,
/// so that we can test the decoding of it.
async fn submit_extrinsic_and_get_it_back(
api: &subxt::OnlineClient<SubstrateConfig>,
params: subxt::config::DefaultExtrinsicParamsBuilder<SubstrateConfig>,
) -> subxt::blocks::ExtrinsicDetails<SubstrateConfig, subxt::OnlineClient<SubstrateConfig>> {
let alice = dev::alice();
let bob = dev::bob();
let tx = node_runtime::tx()
.balances()
.transfer_allow_death(bob.public_key().into(), 10_000);
let signed_extrinsic = api
.tx()
.create_signed(&tx, &alice, params.build())
.await
.unwrap();
let in_block = signed_extrinsic
.submit_and_watch()
.await
.unwrap()
.wait_for_finalized()
.await
.unwrap();
let block_hash = in_block.block_hash();
let block = api.blocks().at(block_hash).await.unwrap();
let extrinsics = block.extrinsics().await.unwrap();
extrinsics.iter().find(|e| e.is_signed()).unwrap()
}
#[cfg(fullclient)]
#[subxt_test]
async fn decode_transaction_extensions_from_blocks() {
let ctx = test_context().await;
let api = ctx.client();
let transaction1 =
submit_extrinsic_and_get_it_back(&api, DefaultExtrinsicParamsBuilder::new().tip(1234))
.await;
let extensions1 = transaction1.transaction_extensions().unwrap();
let nonce1 = extensions1.nonce().unwrap();
let nonce1_static = extensions1.find::<CheckNonce>().unwrap().unwrap();
let tip1 = extensions1.tip().unwrap();
let tip1_static: u128 = extensions1
.find::<ChargeAssetTxPayment<SubstrateConfig>>()
.unwrap()
.unwrap()
.tip();
let transaction2 =
submit_extrinsic_and_get_it_back(&api, DefaultExtrinsicParamsBuilder::new().tip(5678))
.await;
let extensions2 = transaction2.transaction_extensions().unwrap();
let nonce2 = extensions2.nonce().unwrap();
let nonce2_static = extensions2.find::<CheckNonce>().unwrap().unwrap();
let tip2 = extensions2.tip().unwrap();
let tip2_static: u128 = extensions2
.find::<ChargeAssetTxPayment<SubstrateConfig>>()
.unwrap()
.unwrap()
.tip();
assert_eq!(nonce1, 0);
assert_eq!(nonce1, nonce1_static);
assert_eq!(tip1, 1234);
assert_eq!(tip1, tip1_static);
assert_eq!(nonce2, 1);
assert_eq!(nonce2, nonce2_static);
assert_eq!(tip2, 5678);
assert_eq!(tip2, tip2_static);
let expected_transaction_extensions = [
"AuthorizeCall",
"CheckNonZeroSender",
"CheckSpecVersion",
"CheckTxVersion",
"CheckGenesis",
"CheckMortality",
"CheckNonce",
"CheckWeight",
"ChargeAssetTxPayment",
"CheckMetadataHash",
"EthSetOrigin",
"WeightReclaim",
];
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_transaction_extensions.len()
);
for (e, expected_name) in extensions2
.iter()
.zip(expected_transaction_extensions.iter())
{
assert_eq!(e.name(), *expected_name);
}
}
#[cfg(fullclient)]
#[subxt_test]
async fn decode_block_mortality() {
let ctx = test_context().await;
let api = ctx.client();
// Explicit Immortal:
{
let tx =
submit_extrinsic_and_get_it_back(&api, DefaultExtrinsicParamsBuilder::new().immortal())
.await;
let mortality = tx
.transaction_extensions()
.unwrap()
.find::<CheckMortality<SubstrateConfig>>()
.unwrap()
.unwrap();
assert_eq!(mortality, Era::Immortal);
}
// Explicit Mortal:
for for_n_blocks in [16, 64, 128] {
let tx = submit_extrinsic_and_get_it_back(
&api,
DefaultExtrinsicParamsBuilder::new().mortal(for_n_blocks),
)
.await;
let mortality = tx
.transaction_extensions()
.unwrap()
.find::<CheckMortality<SubstrateConfig>>()
.unwrap()
.unwrap();
assert!(matches!(mortality, Era::Mortal {
period,
phase: _, // depends on current block so don't test it.
} if period == for_n_blocks));
}
// Implicitly, transactions should be mortal:
{
let tx =
submit_extrinsic_and_get_it_back(&api, DefaultExtrinsicParamsBuilder::default()).await;
let mortality = tx
.transaction_extensions()
.unwrap()
.find::<CheckMortality<SubstrateConfig>>()
.unwrap()
.unwrap();
assert!(matches!(
mortality,
Era::Mortal {
period: 32,
phase: _, // depends on current block so don't test it.
}
));
}
}