## subxt Test with Smoldot + ChainHeadBackend The below example is using: - `Smoldot`: since I create the light client with `LightClient::relay_chain` - `ChainHeadBackend`: since I use `ChainHeadBackendBuilder::default().build_with_background_driver(polkadot_rpc...` ### Test Goal The goal of this test is to check if when using `Smoldot` + `ChainHeadBackend`, we get duplicates in bounties storage keys. ### Example / Test #### Cargo.toml ```toml [dependencies] subxt-lightclient = "0.44.0" subxt = { version = "0.44.0", features = ["unstable-light-client"] } subxt-rpcs = { version = "0.44.0", features = ["unstable-light-client"] } smoldot = "0.20" # used latest smoldot-light = "0.18" # used latest tokio = { version = "1", features = ["full"] } tracing-subscriber = "0.3" futures = "0.3.31" codec = { package = "parity-scale-codec", version = "3" } hex = "0.4.3" ``` #### main.rs ```rust #![allow(missing_docs)] use subxt::{client::OnlineClient, lightclient::LightClient, PolkadotConfig}; use std::collections::BTreeMap; use subxt::backend::chain_head::{ChainHeadBackend, ChainHeadBackendBuilder}; #[subxt::subxt(runtime_metadata_path = "artifacts/polkadot_metadata_full.scale")] pub mod polkadot {} const POLKADOT_SPEC: &str = include_str!("../artifacts/demo_chain_specs/polkadot.json"); #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); // Smoldot light client let (_lightclient, polkadot_rpc) = LightClient::relay_chain(POLKADOT_SPEC)?; // Convert to unstable backend (ChainHead) let unstable_backend: ChainHeadBackend<PolkadotConfig> = ChainHeadBackendBuilder::default().build_with_background_driver(polkadot_rpc.clone()); let api = OnlineClient::from_backend(std::sync::Arc::new(unstable_backend)).await?; let address = polkadot::storage() .bounties() .bounties_iter(); let mut iter = api.storage().at_latest().await?.iter(address).await?; let mut data = BTreeMap::new(); let mut total_count = 0; while let Some(Ok(storage)) = iter.next().await { total_count += 1; let id = get_bounty_id_from_storage_key(storage.key_bytes.clone()); if data.contains_key(&id) { println!("āš ļø Duplicate detected! ID: {} at item #{}", id, total_count); println!(" Key: 0x{}", hex::encode(&storage.key_bytes)); } data.entry(id).and_modify(|n| *n += 1).or_insert(1); } println!("\nšŸ“Š Final Results:"); println!("Total items: {}", total_count); println!("Unique IDs: {}", data.len()); println!("Duplicates: {}", total_count - data.len()); for (id, count) in data.iter() { if *count > 1 { println!(" ID {} appeared {} times", id, count); } } Ok(()) } pub fn get_bounty_id_from_storage_key(key: Vec<u8>) -> u32 { let s = &key[key.len() - 4..]; let v: [u8; 4] = s.try_into().expect("slice with incorrect length"); u32::from_le_bytes(v) } ``` ## Outputs ### Output after Multiple Runs Usually, during consecutive runs we get the same result (shown below) hence we find no duplicates in the bounties storage keys. ``` 2025-10-23T09:55:14.577227Z INFO runtime-polkadot: Finalized block runtime ready. Spec version: 1007001. Size of `:code`: 2.0 MiB. šŸ“Š Final Results: Total items: 23 Unique IDs: 23 Duplicates: 0 ``` ### Output in Some Runs There are some runs during which the output hangs. It seems that it is happening whenever I see the following `Successfully compiled runtime. Spec version: 1007001` in the logs. So in the terminal I see: ``` 2025-10-23T09:50:24.151507Z INFO runtime-polkadot: Successfully compiled runtime. Spec version: 1006001. Size of `:code`: 1.9 MiB. 2025-10-23T09:50:36.002975Z INFO runtime-polkadot: Finalized block runtime ready. Spec version: 1007001. Size of `:code`: 2.0 MiB. 2025-10-23T09:50:38.453057Z INFO runtime-polkadot: Successfully compiled runtime. Spec version: 1007001. Size of `:code`: 2.0 MiB. ``` and then no further output and the program does not terminate.