From b3d8daf98cb8d97ac6301dc1d61cb5c8b762a097 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 6 May 2026 17:32:05 +0100 Subject: [PATCH 01/18] add extra logs to state --- core/src/common/state.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/common/state.rs b/core/src/common/state.rs index 1dc9ac29854..e856116070d 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -35,7 +35,7 @@ use sp_runtime::{ DeserializeOwned, }; use sp_state_machine::{OverlayedChanges, StateMachine, TestExternalities, TrieBackendBuilder}; -use substrate_rpc_client::{ws_client, ChainApi}; +use substrate_rpc_client::{ws_client, ChainApi, SystemApi}; use crate::{ common::{ @@ -111,18 +111,29 @@ impl LiveState { { // We want to execute the block `at`, therefore need the state of the block *before* it. let at = self.at::()?; + log::debug!(target: LOG_TARGET, "Fetching previous block state relative to block {:?}", at); // Get the block number requested by the user, or the current block number if they // didn't specify one. let rpc = ws_client(&self.uri[0]).await?; + + let chain = SystemApi::<(), ()>::system_chain(&rpc).await.map_err(rpc_err_handler)?; + let name = SystemApi::<(), ()>::system_name(&rpc).await.map_err(rpc_err_handler)?; + let version = SystemApi::<(), ()>::system_version(&rpc).await.map_err(rpc_err_handler)?; + log::info!(target: LOG_TARGET, "Connected to '{chain}' (node: {name} v{version}) at {}", &self.uri[0]); + let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at) .await .map_err(rpc_err_handler) .and_then(|maybe_header| { maybe_header .ok_or("header_not_found") - .map(|h| *h.parent_hash()) + .map(|h| { + log::debug!(target: LOG_TARGET, "Header at block {:?}: {:?}", at, h); + *h.parent_hash() + }) })?; + log::debug!(target: LOG_TARGET, "Resolved previous block hash: {:?}", previous_hash); Ok(LiveState { at: Some(hex::encode(previous_hash)), From ff5b7f55911c0cadd4734af53e9d67b3e7fe5cd1 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 6 May 2026 18:15:28 +0100 Subject: [PATCH 02/18] add logging --- core/src/common/state.rs | 251 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 5 deletions(-) diff --git a/core/src/common/state.rs b/core/src/common/state.rs index e856116070d..1e969a63070 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -183,12 +183,42 @@ impl State { Block::Header: DeserializeOwned, ::Err: Debug, { + log::debug!( + target: LOG_TARGET, + "to_ext: runtime_checks={{ name_matches={}, version_increases={}, try_runtime_feature_enabled={} }}", + runtime_checks.name_matches, + runtime_checks.version_increases, + runtime_checks.try_runtime_feature_enabled, + ); + log::debug!( + target: LOG_TARGET, + "to_ext: shared={{ runtime={:?}, heap_pages={:?}, overwrite_state_version={:?} }}", + shared.runtime, + shared.heap_pages, + shared.overwrite_state_version, + ); + let builder = match self { State::Snap { path } => { let path = path .as_ref() .ok_or_else(|| "no snapshot path provided".to_string())?; + log::info!(target: LOG_TARGET, "[Snap] Reading state from snapshot: {:?}", path); + match std::fs::metadata(path) { + Ok(meta) => log::info!( + target: LOG_TARGET, + "[Snap] Snapshot file size: {} bytes ({:.2} MiB)", + meta.len(), + meta.len() as f64 / 1_048_576.0, + ), + Err(e) => log::warn!( + target: LOG_TARGET, + "[Snap] Could not stat snapshot file: {:?}", + e, + ), + } + Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(path), })) @@ -200,8 +230,96 @@ impl State { child_tree, hashed_prefixes, }) => { + log::info!(target: LOG_TARGET, "[Live] Connecting to live chain"); + log::info!( + target: LOG_TARGET, + "[Live] Transport URIs ({} endpoint(s)): {:?}", + uri.len(), + uri, + ); + + match at.as_deref() { + Some(at_str) => log::info!( + target: LOG_TARGET, + "[Live] Fetching state at explicit block hash string: {}", + at_str, + ), + None => log::info!( + target: LOG_TARGET, + "[Live] No --at provided; will use the latest finalized head", + ), + } + + if pallet.is_empty() { + log::info!( + target: LOG_TARGET, + "[Live] No specific pallets requested — entire chain state will be scraped", + ); + } else { + log::info!( + target: LOG_TARGET, + "[Live] Scraping {} pallet(s): {:?}", + pallet.len(), + pallet, + ); + for p in pallet.iter() { + log::debug!( + target: LOG_TARGET, + "[Live] Pallet '{}' -> storage prefix: 0x{}", + p, + hex::encode(twox_128(p.as_bytes())), + ); + } + } + + if hashed_prefixes.is_empty() { + log::debug!(target: LOG_TARGET, "[Live] No extra --prefix entries specified"); + } else { + log::info!( + target: LOG_TARGET, + "[Live] Extra hashed_prefixes ({}): {:?}", + hashed_prefixes.len(), + hashed_prefixes, + ); + } + + log::debug!(target: LOG_TARGET, "[Live] child_tree: {}", child_tree); + + log::debug!( + target: LOG_TARGET, + "[Live] Always-fetched hashed_keys: \ + CODE=0x{} | \ + System::LastRuntimeUpgrade=0x{} | \ + System::Number=0x{}", + hex::encode(well_known_keys::CODE), + hex::encode( + [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat() + ), + hex::encode([twox_128(b"System"), twox_128(b"Number")].concat()), + ); + + match &state_snapshot { + Some(snap) => log::info!( + target: LOG_TARGET, + "[Live] Downloaded state will be cached to snapshot: {:?}", + snap, + ), + None => log::debug!( + target: LOG_TARGET, + "[Live] No snapshot cache configured; state will not be persisted to disk", + ), + } + let at = match at { - Some(at_str) => Some(hash_of::(at_str)?), + Some(at_str) => { + let hash = hash_of::(at_str)?; + log::info!( + target: LOG_TARGET, + "[Live] Resolved block hash: {:?}", + hash, + ); + Some(hash) + } None => None, }; let hashed_prefixes = hashed_prefixes @@ -249,24 +367,82 @@ impl State { // then, we prepare to replace the code based on what the CLI wishes. let maybe_code_to_overwrite = match shared.runtime { - Runtime::Path(ref path) => Some(std::fs::read(path).map_err(|e| { - format!("error while reading runtime file from {:?}: {:?}", path, e) - })?), - Runtime::Existing => None, + Runtime::Path(ref path) => { + log::info!( + target: LOG_TARGET, + "[Runtime] Reading custom WASM runtime from disk: {:?}", + path, + ); + let code = std::fs::read(path).map_err(|e| { + format!("error while reading runtime file from {:?}: {:?}", path, e) + })?; + log::info!( + target: LOG_TARGET, + "[Runtime] Custom WASM loaded: {} bytes ({:.2} MiB)", + code.len(), + code.len() as f64 / 1_048_576.0, + ); + Some(code) + } + Runtime::Existing => { + log::info!( + target: LOG_TARGET, + "[Runtime] Using existing on-chain runtime (no WASM override)", + ); + None + } }; // build the main ext. + log::info!( + target: LOG_TARGET, + "Building externalities — network I/O (Live) or disk read (Snap) starts now", + ); + let build_start = std::time::Instant::now(); let mut ext = builder.build().await?; + log::info!( + target: LOG_TARGET, + "Externalities built in {:.2?}", + build_start.elapsed(), + ); // actually replace the code if needed. if let Some(new_code) = maybe_code_to_overwrite { + log::info!( + target: LOG_TARGET, + "[CodeOverride] Replacing on-chain runtime with custom WASM", + ); + let original_code = ext .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); + log::debug!( + target: LOG_TARGET, + "[CodeOverride] On-chain code size: {} bytes ({:.2} MiB)", + original_code.len(), + original_code.len() as f64 / 1_048_576.0, + ); + log::debug!( + target: LOG_TARGET, + "[CodeOverride] Custom code size: {} bytes ({:.2} MiB)", + new_code.len(), + new_code.len() as f64 / 1_048_576.0, + ); + // NOTE: see the impl notes of `read_runtime_version`, the ext is almost not used here, // only as a backup. + log::debug!( + target: LOG_TARGET, + "[CodeOverride] Writing new WASM to storage key 0x{}", + hex::encode(well_known_keys::CODE), + ); ext.insert(well_known_keys::CODE.to_vec(), new_code.clone()); + + log::debug!( + target: LOG_TARGET, + "[CodeOverride] Decoding on-chain runtime version via executor", + ); let old_version = ::decode( &mut &*executor .read_runtime_version(&original_code, &mut ext.ext()) @@ -288,6 +464,21 @@ impl State { "Original runtime full code hash: 0x{:?}", old_code_hash, ); + log::debug!( + target: LOG_TARGET, + "[CodeOverride] Original runtime: impl_name={:?} impl_version={} \ + authoring_version={} transaction_version={} state_version={}", + old_version.impl_name, + old_version.impl_version, + old_version.authoring_version, + old_version.transaction_version, + old_version.state_version, + ); + + log::debug!( + target: LOG_TARGET, + "[CodeOverride] Decoding new runtime version via executor", + ); let new_version = ::decode( &mut &*executor .read_runtime_version(&new_code, &mut ext.ext()) @@ -309,27 +500,77 @@ impl State { "New runtime code hash: 0x{:?}", new_code_hash ); + log::debug!( + target: LOG_TARGET, + "[CodeOverride] New runtime: impl_name={:?} impl_version={} \ + authoring_version={} transaction_version={} state_version={}", + new_version.impl_name, + new_version.impl_version, + new_version.authoring_version, + new_version.transaction_version, + new_version.state_version, + ); if runtime_checks.name_matches && new_version.spec_name != old_version.spec_name { + log::error!( + target: LOG_TARGET, + "[RuntimeCheck] spec_name mismatch: on-chain={:?} custom={:?}", + old_version.spec_name, + new_version.spec_name, + ); return Err( "Spec names must match. Use `--disable-spec-name-check` to disable this check." .into(), ); } + log::debug!( + target: LOG_TARGET, + "[RuntimeCheck] name_matches: check_enabled={} on-chain={:?} custom={:?} -> {}", + runtime_checks.name_matches, + old_version.spec_name, + new_version.spec_name, + if new_version.spec_name == old_version.spec_name { "PASS" } else { "SKIP (check disabled)" }, + ); if runtime_checks.version_increases && new_version.spec_version <= old_version.spec_version { + log::error!( + target: LOG_TARGET, + "[RuntimeCheck] spec_version did not increase: on-chain={} custom={}", + old_version.spec_version, + new_version.spec_version, + ); return Err(format!("New runtime spec version must be greater than the on-chain runtime spec version: {} <= {}. Use `--disable-spec-version-check` to disable this check.", new_version.spec_version, old_version.spec_version).into()); } + log::debug!( + target: LOG_TARGET, + "[RuntimeCheck] version_increases: check_enabled={} on-chain={} custom={} -> {}", + runtime_checks.version_increases, + old_version.spec_version, + new_version.spec_version, + if !runtime_checks.version_increases { "SKIP (check disabled)" } + else if new_version.spec_version > old_version.spec_version { "PASS" } + else { "FAIL" }, + ); } if runtime_checks.try_runtime_feature_enabled && !ensure_try_runtime::(executor, &mut ext) { + log::error!( + target: LOG_TARGET, + "[RuntimeCheck] try_runtime feature is not present in the given runtime", + ); return Err("Given runtime is not compiled with the try-runtime feature.".into()); } + log::debug!( + target: LOG_TARGET, + "[RuntimeCheck] try_runtime_feature_enabled: check_enabled={}", + runtime_checks.try_runtime_feature_enabled, + ); + log::info!(target: LOG_TARGET, "to_ext: done"); Ok(ext) } } From 31409302fe2431e0c15f1a1c68eb94cdfa0554db Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 6 May 2026 18:20:50 +0100 Subject: [PATCH 03/18] update --- core/src/common/state.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/common/state.rs b/core/src/common/state.rs index 1e969a63070..d8e712e7bb0 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -299,10 +299,9 @@ impl State { ); match &state_snapshot { - Some(snap) => log::info!( + Some(_) => log::info!( target: LOG_TARGET, - "[Live] Downloaded state will be cached to snapshot: {:?}", - snap, + "[Live] Downloaded state will be cached to a local snapshot", ), None => log::debug!( target: LOG_TARGET, @@ -472,7 +471,7 @@ impl State { old_version.impl_version, old_version.authoring_version, old_version.transaction_version, - old_version.state_version, + old_version.state_version(), ); log::debug!( @@ -508,7 +507,7 @@ impl State { new_version.impl_version, new_version.authoring_version, new_version.transaction_version, - new_version.state_version, + new_version.state_version(), ); if runtime_checks.name_matches && new_version.spec_name != old_version.spec_name { From 9371c41eabf68a96c97b0d8482163ebeef299cb2 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Tue, 12 May 2026 14:43:50 +0100 Subject: [PATCH 04/18] remove debug logs --- core/src/common/state.rs | 250 +-------------------------------------- 1 file changed, 5 insertions(+), 245 deletions(-) diff --git a/core/src/common/state.rs b/core/src/common/state.rs index d8e712e7bb0..e856116070d 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -183,42 +183,12 @@ impl State { Block::Header: DeserializeOwned, ::Err: Debug, { - log::debug!( - target: LOG_TARGET, - "to_ext: runtime_checks={{ name_matches={}, version_increases={}, try_runtime_feature_enabled={} }}", - runtime_checks.name_matches, - runtime_checks.version_increases, - runtime_checks.try_runtime_feature_enabled, - ); - log::debug!( - target: LOG_TARGET, - "to_ext: shared={{ runtime={:?}, heap_pages={:?}, overwrite_state_version={:?} }}", - shared.runtime, - shared.heap_pages, - shared.overwrite_state_version, - ); - let builder = match self { State::Snap { path } => { let path = path .as_ref() .ok_or_else(|| "no snapshot path provided".to_string())?; - log::info!(target: LOG_TARGET, "[Snap] Reading state from snapshot: {:?}", path); - match std::fs::metadata(path) { - Ok(meta) => log::info!( - target: LOG_TARGET, - "[Snap] Snapshot file size: {} bytes ({:.2} MiB)", - meta.len(), - meta.len() as f64 / 1_048_576.0, - ), - Err(e) => log::warn!( - target: LOG_TARGET, - "[Snap] Could not stat snapshot file: {:?}", - e, - ), - } - Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(path), })) @@ -230,95 +200,8 @@ impl State { child_tree, hashed_prefixes, }) => { - log::info!(target: LOG_TARGET, "[Live] Connecting to live chain"); - log::info!( - target: LOG_TARGET, - "[Live] Transport URIs ({} endpoint(s)): {:?}", - uri.len(), - uri, - ); - - match at.as_deref() { - Some(at_str) => log::info!( - target: LOG_TARGET, - "[Live] Fetching state at explicit block hash string: {}", - at_str, - ), - None => log::info!( - target: LOG_TARGET, - "[Live] No --at provided; will use the latest finalized head", - ), - } - - if pallet.is_empty() { - log::info!( - target: LOG_TARGET, - "[Live] No specific pallets requested — entire chain state will be scraped", - ); - } else { - log::info!( - target: LOG_TARGET, - "[Live] Scraping {} pallet(s): {:?}", - pallet.len(), - pallet, - ); - for p in pallet.iter() { - log::debug!( - target: LOG_TARGET, - "[Live] Pallet '{}' -> storage prefix: 0x{}", - p, - hex::encode(twox_128(p.as_bytes())), - ); - } - } - - if hashed_prefixes.is_empty() { - log::debug!(target: LOG_TARGET, "[Live] No extra --prefix entries specified"); - } else { - log::info!( - target: LOG_TARGET, - "[Live] Extra hashed_prefixes ({}): {:?}", - hashed_prefixes.len(), - hashed_prefixes, - ); - } - - log::debug!(target: LOG_TARGET, "[Live] child_tree: {}", child_tree); - - log::debug!( - target: LOG_TARGET, - "[Live] Always-fetched hashed_keys: \ - CODE=0x{} | \ - System::LastRuntimeUpgrade=0x{} | \ - System::Number=0x{}", - hex::encode(well_known_keys::CODE), - hex::encode( - [twox_128(b"System"), twox_128(b"LastRuntimeUpgrade")].concat() - ), - hex::encode([twox_128(b"System"), twox_128(b"Number")].concat()), - ); - - match &state_snapshot { - Some(_) => log::info!( - target: LOG_TARGET, - "[Live] Downloaded state will be cached to a local snapshot", - ), - None => log::debug!( - target: LOG_TARGET, - "[Live] No snapshot cache configured; state will not be persisted to disk", - ), - } - let at = match at { - Some(at_str) => { - let hash = hash_of::(at_str)?; - log::info!( - target: LOG_TARGET, - "[Live] Resolved block hash: {:?}", - hash, - ); - Some(hash) - } + Some(at_str) => Some(hash_of::(at_str)?), None => None, }; let hashed_prefixes = hashed_prefixes @@ -366,82 +249,24 @@ impl State { // then, we prepare to replace the code based on what the CLI wishes. let maybe_code_to_overwrite = match shared.runtime { - Runtime::Path(ref path) => { - log::info!( - target: LOG_TARGET, - "[Runtime] Reading custom WASM runtime from disk: {:?}", - path, - ); - let code = std::fs::read(path).map_err(|e| { - format!("error while reading runtime file from {:?}: {:?}", path, e) - })?; - log::info!( - target: LOG_TARGET, - "[Runtime] Custom WASM loaded: {} bytes ({:.2} MiB)", - code.len(), - code.len() as f64 / 1_048_576.0, - ); - Some(code) - } - Runtime::Existing => { - log::info!( - target: LOG_TARGET, - "[Runtime] Using existing on-chain runtime (no WASM override)", - ); - None - } + Runtime::Path(ref path) => Some(std::fs::read(path).map_err(|e| { + format!("error while reading runtime file from {:?}: {:?}", path, e) + })?), + Runtime::Existing => None, }; // build the main ext. - log::info!( - target: LOG_TARGET, - "Building externalities — network I/O (Live) or disk read (Snap) starts now", - ); - let build_start = std::time::Instant::now(); let mut ext = builder.build().await?; - log::info!( - target: LOG_TARGET, - "Externalities built in {:.2?}", - build_start.elapsed(), - ); // actually replace the code if needed. if let Some(new_code) = maybe_code_to_overwrite { - log::info!( - target: LOG_TARGET, - "[CodeOverride] Replacing on-chain runtime with custom WASM", - ); - let original_code = ext .execute_with(|| sp_io::storage::get(well_known_keys::CODE)) .expect("':CODE:' is always downloaded in try-runtime-cli; qed"); - log::debug!( - target: LOG_TARGET, - "[CodeOverride] On-chain code size: {} bytes ({:.2} MiB)", - original_code.len(), - original_code.len() as f64 / 1_048_576.0, - ); - log::debug!( - target: LOG_TARGET, - "[CodeOverride] Custom code size: {} bytes ({:.2} MiB)", - new_code.len(), - new_code.len() as f64 / 1_048_576.0, - ); - // NOTE: see the impl notes of `read_runtime_version`, the ext is almost not used here, // only as a backup. - log::debug!( - target: LOG_TARGET, - "[CodeOverride] Writing new WASM to storage key 0x{}", - hex::encode(well_known_keys::CODE), - ); ext.insert(well_known_keys::CODE.to_vec(), new_code.clone()); - - log::debug!( - target: LOG_TARGET, - "[CodeOverride] Decoding on-chain runtime version via executor", - ); let old_version = ::decode( &mut &*executor .read_runtime_version(&original_code, &mut ext.ext()) @@ -463,21 +288,6 @@ impl State { "Original runtime full code hash: 0x{:?}", old_code_hash, ); - log::debug!( - target: LOG_TARGET, - "[CodeOverride] Original runtime: impl_name={:?} impl_version={} \ - authoring_version={} transaction_version={} state_version={}", - old_version.impl_name, - old_version.impl_version, - old_version.authoring_version, - old_version.transaction_version, - old_version.state_version(), - ); - - log::debug!( - target: LOG_TARGET, - "[CodeOverride] Decoding new runtime version via executor", - ); let new_version = ::decode( &mut &*executor .read_runtime_version(&new_code, &mut ext.ext()) @@ -499,77 +309,27 @@ impl State { "New runtime code hash: 0x{:?}", new_code_hash ); - log::debug!( - target: LOG_TARGET, - "[CodeOverride] New runtime: impl_name={:?} impl_version={} \ - authoring_version={} transaction_version={} state_version={}", - new_version.impl_name, - new_version.impl_version, - new_version.authoring_version, - new_version.transaction_version, - new_version.state_version(), - ); if runtime_checks.name_matches && new_version.spec_name != old_version.spec_name { - log::error!( - target: LOG_TARGET, - "[RuntimeCheck] spec_name mismatch: on-chain={:?} custom={:?}", - old_version.spec_name, - new_version.spec_name, - ); return Err( "Spec names must match. Use `--disable-spec-name-check` to disable this check." .into(), ); } - log::debug!( - target: LOG_TARGET, - "[RuntimeCheck] name_matches: check_enabled={} on-chain={:?} custom={:?} -> {}", - runtime_checks.name_matches, - old_version.spec_name, - new_version.spec_name, - if new_version.spec_name == old_version.spec_name { "PASS" } else { "SKIP (check disabled)" }, - ); if runtime_checks.version_increases && new_version.spec_version <= old_version.spec_version { - log::error!( - target: LOG_TARGET, - "[RuntimeCheck] spec_version did not increase: on-chain={} custom={}", - old_version.spec_version, - new_version.spec_version, - ); return Err(format!("New runtime spec version must be greater than the on-chain runtime spec version: {} <= {}. Use `--disable-spec-version-check` to disable this check.", new_version.spec_version, old_version.spec_version).into()); } - log::debug!( - target: LOG_TARGET, - "[RuntimeCheck] version_increases: check_enabled={} on-chain={} custom={} -> {}", - runtime_checks.version_increases, - old_version.spec_version, - new_version.spec_version, - if !runtime_checks.version_increases { "SKIP (check disabled)" } - else if new_version.spec_version > old_version.spec_version { "PASS" } - else { "FAIL" }, - ); } if runtime_checks.try_runtime_feature_enabled && !ensure_try_runtime::(executor, &mut ext) { - log::error!( - target: LOG_TARGET, - "[RuntimeCheck] try_runtime feature is not present in the given runtime", - ); return Err("Given runtime is not compiled with the try-runtime feature.".into()); } - log::debug!( - target: LOG_TARGET, - "[RuntimeCheck] try_runtime_feature_enabled: check_enabled={}", - runtime_checks.try_runtime_feature_enabled, - ); - log::info!(target: LOG_TARGET, "to_ext: done"); Ok(ext) } } From a9949f43bf4c154a98c55c5fff4c9f43050c7eb7 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 19:18:54 +0100 Subject: [PATCH 05/18] setup offchain worker test scaffolding --- cli/tests/offchain_worker.rs | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 cli/tests/offchain_worker.rs diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs new file mode 100644 index 00000000000..75be34631e5 --- /dev/null +++ b/cli/tests/offchain_worker.rs @@ -0,0 +1,55 @@ +// This file is part of try-runtime-cli. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(unix)] +#![allow(deprecated)] + +use substrate_cli_test_utils as common; + +#[tokio::test] +async fn offchain_worker_works() { + let port = 45789; + + // Spawn a dev node. + let _ = std::thread::spawn(move || { + match common::start_node_inline(vec![ + "--no-hardware-benchmarks", + "--dev", + format!("--rpc-port={}", port).as_str(), + ]) { + Ok(_) => {} + Err(e) => { + panic!("Node exited with error: {}", e); + } + } + }); + + // Wait some time to ensure that the node is warmed up. + std::thread::sleep(Duration::from_secs(90)); + + // Test ok + common::run_with_timeout(Duration::from_secs(60), async move { + + }) + .await; + + // Test attempting to use non-live state. Should terminate with error. + common::run_with_timeout(Duration::from_secs(60), async move { + + }) + .await; +} From c2fa6df4b2f20bb2f31ec1f9e0ada8e70cff1a06 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 19:24:41 +0100 Subject: [PATCH 06/18] import duration --- cli/tests/offchain_worker.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index 75be34631e5..66a73c37815 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -18,6 +18,8 @@ #![cfg(unix)] #![allow(deprecated)] +use std::time::Duration; + use substrate_cli_test_utils as common; #[tokio::test] From 28b4a949f0b52ba6803317f0ac9670869a087d8b Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 19:56:52 +0100 Subject: [PATCH 07/18] add happy path test --- cli/tests/offchain_worker.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index 66a73c37815..cbfbc03bc55 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -21,10 +21,12 @@ use std::time::Duration; use substrate_cli_test_utils as common; +use tokio::process::Command; #[tokio::test] async fn offchain_worker_works() { let port = 45789; + let ws_url = format!("ws://localhost:{}", port); // Spawn a dev node. let _ = std::thread::spawn(move || { @@ -45,7 +47,36 @@ async fn offchain_worker_works() { // Test ok common::run_with_timeout(Duration::from_secs(60), async move { + fn run_offchain_worker(ws_url: &str) -> tokio::process::Child { + let path = cargo_bin("try-runtime"); + assert!( + path.exists(), + "try-runtime binary not found at path: {}", + path.display() + ); + + Command::new(path) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .arg("--runtime=existing") + .args(["offchain-worker", format("--uri={}", ws_url).as_str()]) + .kill_on_drop(true) + .spawn() + .unwrap() + } + + // Try to run offchain-worker command + let mut offchain_execution = run_offchain_worker(&ws_url); + + // Assert that command runs to completion successfully + assert!(offchain_execution + .wait_with_output() + .await + .unwrap() + .status + .success() + ); }) .await; From b9d78ba4257bf4a1d2a293f3fac8eb13b489c52f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 20:16:36 +0100 Subject: [PATCH 08/18] add import --- cli/tests/offchain_worker.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index cbfbc03bc55..6774406477b 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -20,6 +20,7 @@ use std::time::Duration; +use assert_cmd::cargo_bin; use substrate_cli_test_utils as common; use tokio::process::Command; @@ -60,14 +61,14 @@ async fn offchain_worker_works() { .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .arg("--runtime=existing") - .args(["offchain-worker", format("--uri={}", ws_url).as_str()]) + .args(["offchain-worker", format!("--uri={}", ws_url).as_str()]) .kill_on_drop(true) .spawn() .unwrap() } // Try to run offchain-worker command - let mut offchain_execution = run_offchain_worker(&ws_url); + let offchain_execution = run_offchain_worker(&ws_url); // Assert that command runs to completion successfully assert!(offchain_execution From df52160bc41f3e008600e8743d7c342409a4861a Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 20:14:15 +0100 Subject: [PATCH 09/18] cargo fmt --- cli/tests/offchain_worker.rs | 8 ++------ core/src/common/state.rs | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index 6774406477b..b604a0c7da0 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -76,14 +76,10 @@ async fn offchain_worker_works() { .await .unwrap() .status - .success() - ); + .success()); }) .await; // Test attempting to use non-live state. Should terminate with error. - common::run_with_timeout(Duration::from_secs(60), async move { - - }) - .await; + common::run_with_timeout(Duration::from_secs(60), async move {}).await; } diff --git a/core/src/common/state.rs b/core/src/common/state.rs index e856116070d..6843e71a0f9 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -117,21 +117,25 @@ impl LiveState { // didn't specify one. let rpc = ws_client(&self.uri[0]).await?; - let chain = SystemApi::<(), ()>::system_chain(&rpc).await.map_err(rpc_err_handler)?; - let name = SystemApi::<(), ()>::system_name(&rpc).await.map_err(rpc_err_handler)?; - let version = SystemApi::<(), ()>::system_version(&rpc).await.map_err(rpc_err_handler)?; + let chain = SystemApi::<(), ()>::system_chain(&rpc) + .await + .map_err(rpc_err_handler)?; + let name = SystemApi::<(), ()>::system_name(&rpc) + .await + .map_err(rpc_err_handler)?; + let version = SystemApi::<(), ()>::system_version(&rpc) + .await + .map_err(rpc_err_handler)?; log::info!(target: LOG_TARGET, "Connected to '{chain}' (node: {name} v{version}) at {}", &self.uri[0]); let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at) .await .map_err(rpc_err_handler) .and_then(|maybe_header| { - maybe_header - .ok_or("header_not_found") - .map(|h| { - log::debug!(target: LOG_TARGET, "Header at block {:?}: {:?}", at, h); - *h.parent_hash() - }) + maybe_header.ok_or("header_not_found").map(|h| { + log::debug!(target: LOG_TARGET, "Header at block {:?}: {:?}", at, h); + *h.parent_hash() + }) })?; log::debug!(target: LOG_TARGET, "Resolved previous block hash: {:?}", previous_hash); From b99e2d894d0af787a80d34979592f5edf7b60fb3 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 20:23:13 +0100 Subject: [PATCH 10/18] cargo_bin is a macro --- cli/tests/offchain_worker.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index b604a0c7da0..113acacafde 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -49,7 +49,7 @@ async fn offchain_worker_works() { // Test ok common::run_with_timeout(Duration::from_secs(60), async move { fn run_offchain_worker(ws_url: &str) -> tokio::process::Child { - let path = cargo_bin("try-runtime"); + let path = cargo_bin!("try-runtime"); assert!( path.exists(), From 866aa3e632d6675740a078220c2baebf07ec3cee Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 13 May 2026 20:41:09 +0100 Subject: [PATCH 11/18] update command arguments --- cli/tests/offchain_worker.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index 113acacafde..e882c921b90 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -61,7 +61,8 @@ async fn offchain_worker_works() { .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .arg("--runtime=existing") - .args(["offchain-worker", format!("--uri={}", ws_url).as_str()]) + .arg("offchain-worker") + .args(["live", format!("--uri={}", ws_url).as_str()]) .kill_on_drop(true) .spawn() .unwrap() From 05782898cc2e2efb43f5df81053a6d0add8ea7c4 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 02:18:06 +0100 Subject: [PATCH 12/18] add error state test for offchain worker --- cli/tests/offchain_worker.rs | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index e882c921b90..bb490a56459 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -82,5 +82,44 @@ async fn offchain_worker_works() { .await; // Test attempting to use non-live state. Should terminate with error. - common::run_with_timeout(Duration::from_secs(60), async move {}).await; + common::run_with_timeout(Duration::from_secs(60), async move { + fn run_offchain_worker_snap_state(ws_url: &str) -> tokio::process::Child { + let path = cargo_bin!("try-runtime"); + + assert!( + path.exists(), + "try-runtime binary not found at path: {}", + path.display() + ); + + let project_root = env!("CARGO_MANIFEST_DIR"); + let snap_file_path = format!("{}/tests/snaps/people_rococo_runtime_ok.compact.compressed.wasm", project_root); + + Command::new(path) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .arg("--runtime=existing") + .arg("offchain_worker") + .args(["snap", "--path", snap_file_path.as_str()]) + .kill_on_drop(true) + .spawn() + .unwrap() + } + + // Try to execute command + let mut offchain_execution = run_offchain_worker_snap_state(&ws_url); + let expected_output = r"execute block currently only supports Live state"; + let re = Regex::new(expected_output).unwrap(); + let matched = common::wait_for_stream_pattern_match(offchain_execution.stderr.take().unwrap(), re).await; + + assert!(matched.is_ok()); + + // Assert that the command did not exit successfully + assert!(!offchain_execution + .wait_with_output() + .await + .unwrap() + .status + .success()); + }).await; } From cda0204abddbd3dbae6682ed79f8f7e3eddb8cf6 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 02:27:56 +0100 Subject: [PATCH 13/18] refactor --- cli/tests/offchain_worker.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index bb490a56459..6abea9f3e77 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -21,13 +21,13 @@ use std::time::Duration; use assert_cmd::cargo_bin; +use regex::Regex; use substrate_cli_test_utils as common; use tokio::process::Command; #[tokio::test] async fn offchain_worker_works() { let port = 45789; - let ws_url = format!("ws://localhost:{}", port); // Spawn a dev node. let _ = std::thread::spawn(move || { @@ -48,6 +48,8 @@ async fn offchain_worker_works() { // Test ok common::run_with_timeout(Duration::from_secs(60), async move { + let ws_url = format!("ws://localhost:{}", port); + fn run_offchain_worker(ws_url: &str) -> tokio::process::Child { let path = cargo_bin!("try-runtime"); @@ -83,7 +85,7 @@ async fn offchain_worker_works() { // Test attempting to use non-live state. Should terminate with error. common::run_with_timeout(Duration::from_secs(60), async move { - fn run_offchain_worker_snap_state(ws_url: &str) -> tokio::process::Child { + fn run_offchain_worker_snap_state() -> tokio::process::Child { let path = cargo_bin!("try-runtime"); assert!( @@ -107,7 +109,7 @@ async fn offchain_worker_works() { } // Try to execute command - let mut offchain_execution = run_offchain_worker_snap_state(&ws_url); + let mut offchain_execution = run_offchain_worker_snap_state(); let expected_output = r"execute block currently only supports Live state"; let re = Regex::new(expected_output).unwrap(); let matched = common::wait_for_stream_pattern_match(offchain_execution.stderr.take().unwrap(), re).await; From 2cd947b88c6106daf3d9a4ebb5430f9a42db1dee Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 02:57:12 +0100 Subject: [PATCH 14/18] update --- cli/tests/offchain_worker.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index 6abea9f3e77..1fe3151ece4 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -28,6 +28,7 @@ use tokio::process::Command; #[tokio::test] async fn offchain_worker_works() { let port = 45789; + let ws_url = format!("ws://localhost:{}", port); // Spawn a dev node. let _ = std::thread::spawn(move || { @@ -48,8 +49,6 @@ async fn offchain_worker_works() { // Test ok common::run_with_timeout(Duration::from_secs(60), async move { - let ws_url = format!("ws://localhost:{}", port); - fn run_offchain_worker(ws_url: &str) -> tokio::process::Child { let path = cargo_bin!("try-runtime"); @@ -85,7 +84,7 @@ async fn offchain_worker_works() { // Test attempting to use non-live state. Should terminate with error. common::run_with_timeout(Duration::from_secs(60), async move { - fn run_offchain_worker_snap_state() -> tokio::process::Child { + fn run_offchain_worker_snap_state(ws_url: &str) -> tokio::process::Child { let path = cargo_bin!("try-runtime"); assert!( @@ -95,13 +94,14 @@ async fn offchain_worker_works() { ); let project_root = env!("CARGO_MANIFEST_DIR"); - let snap_file_path = format!("{}/tests/snaps/people_rococo_runtime_ok.compact.compressed.wasm", project_root); + let snap_file_path = format!("{}/tests/snaps/rococo-people.snap", project_root); Command::new(path) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .arg("--runtime=existing") - .arg("offchain_worker") + .arg("offchain-worker") + .args(["--header-ws-uri", ws_url]) .args(["snap", "--path", snap_file_path.as_str()]) .kill_on_drop(true) .spawn() @@ -109,7 +109,7 @@ async fn offchain_worker_works() { } // Try to execute command - let mut offchain_execution = run_offchain_worker_snap_state(); + let mut offchain_execution = run_offchain_worker_snap_state(&ws_url); let expected_output = r"execute block currently only supports Live state"; let re = Regex::new(expected_output).unwrap(); let matched = common::wait_for_stream_pattern_match(offchain_execution.stderr.take().unwrap(), re).await; From 7be02c0e64f05ddb3ca8f53063c5bbfbcff2644b Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 03:00:14 +0100 Subject: [PATCH 15/18] refactor --- cli/tests/offchain_worker.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index 1fe3151ece4..bb97487ef1a 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -28,7 +28,6 @@ use tokio::process::Command; #[tokio::test] async fn offchain_worker_works() { let port = 45789; - let ws_url = format!("ws://localhost:{}", port); // Spawn a dev node. let _ = std::thread::spawn(move || { @@ -49,6 +48,8 @@ async fn offchain_worker_works() { // Test ok common::run_with_timeout(Duration::from_secs(60), async move { + let ws_url = format!("ws://localhost:{}", port); + fn run_offchain_worker(ws_url: &str) -> tokio::process::Child { let path = cargo_bin!("try-runtime"); @@ -84,6 +85,8 @@ async fn offchain_worker_works() { // Test attempting to use non-live state. Should terminate with error. common::run_with_timeout(Duration::from_secs(60), async move { + let ws_url = format!("ws://localhost:{}", port); + fn run_offchain_worker_snap_state(ws_url: &str) -> tokio::process::Child { let path = cargo_bin!("try-runtime"); From 324642bac27743e9d1ee42bd64b7afa9fef1ac3e Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 03:03:50 +0100 Subject: [PATCH 16/18] fmt --- cli/tests/offchain_worker.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/tests/offchain_worker.rs b/cli/tests/offchain_worker.rs index bb97487ef1a..fe5f314d7fc 100644 --- a/cli/tests/offchain_worker.rs +++ b/cli/tests/offchain_worker.rs @@ -86,7 +86,7 @@ async fn offchain_worker_works() { // Test attempting to use non-live state. Should terminate with error. common::run_with_timeout(Duration::from_secs(60), async move { let ws_url = format!("ws://localhost:{}", port); - + fn run_offchain_worker_snap_state(ws_url: &str) -> tokio::process::Child { let path = cargo_bin!("try-runtime"); @@ -115,7 +115,9 @@ async fn offchain_worker_works() { let mut offchain_execution = run_offchain_worker_snap_state(&ws_url); let expected_output = r"execute block currently only supports Live state"; let re = Regex::new(expected_output).unwrap(); - let matched = common::wait_for_stream_pattern_match(offchain_execution.stderr.take().unwrap(), re).await; + let matched = + common::wait_for_stream_pattern_match(offchain_execution.stderr.take().unwrap(), re) + .await; assert!(matched.is_ok()); @@ -126,5 +128,6 @@ async fn offchain_worker_works() { .unwrap() .status .success()); - }).await; + }) + .await; } From 612a3205b6fd0e2ce608923cc9cda4b0ff14ba96 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 03:11:11 +0100 Subject: [PATCH 17/18] remove logs from state file --- core/src/common/state.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/core/src/common/state.rs b/core/src/common/state.rs index 6843e71a0f9..aca16d24222 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -35,7 +35,7 @@ use sp_runtime::{ DeserializeOwned, }; use sp_state_machine::{OverlayedChanges, StateMachine, TestExternalities, TrieBackendBuilder}; -use substrate_rpc_client::{ws_client, ChainApi, SystemApi}; +use substrate_rpc_client::{ws_client, ChainApi}; use crate::{ common::{ @@ -111,33 +111,19 @@ impl LiveState { { // We want to execute the block `at`, therefore need the state of the block *before* it. let at = self.at::()?; - log::debug!(target: LOG_TARGET, "Fetching previous block state relative to block {:?}", at); // Get the block number requested by the user, or the current block number if they // didn't specify one. let rpc = ws_client(&self.uri[0]).await?; - let chain = SystemApi::<(), ()>::system_chain(&rpc) - .await - .map_err(rpc_err_handler)?; - let name = SystemApi::<(), ()>::system_name(&rpc) - .await - .map_err(rpc_err_handler)?; - let version = SystemApi::<(), ()>::system_version(&rpc) - .await - .map_err(rpc_err_handler)?; - log::info!(target: LOG_TARGET, "Connected to '{chain}' (node: {name} v{version}) at {}", &self.uri[0]); - let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at) .await .map_err(rpc_err_handler) .and_then(|maybe_header| { - maybe_header.ok_or("header_not_found").map(|h| { - log::debug!(target: LOG_TARGET, "Header at block {:?}: {:?}", at, h); - *h.parent_hash() - }) + maybe_header + .ok_or("header_not_found") + .map(|h| *h.parent_hash()) })?; - log::debug!(target: LOG_TARGET, "Resolved previous block hash: {:?}", previous_hash); Ok(LiveState { at: Some(hex::encode(previous_hash)), From d22fa387aa8b9a651be33944ecc996c3524d0f3f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 14 May 2026 03:12:40 +0100 Subject: [PATCH 18/18] remove newline --- core/src/common/state.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/common/state.rs b/core/src/common/state.rs index aca16d24222..1dc9ac29854 100644 --- a/core/src/common/state.rs +++ b/core/src/common/state.rs @@ -115,7 +115,6 @@ impl LiveState { // Get the block number requested by the user, or the current block number if they // didn't specify one. let rpc = ws_client(&self.uri[0]).await?; - let previous_hash = ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, at) .await .map_err(rpc_err_handler)