Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0371651
add from and to arguments
naijauser May 18, 2026
b1ecf98
extract execute block logic to helper function
naijauser May 18, 2026
079652f
add trait bounds
naijauser May 19, 2026
e8267fa
add remaining trait bounds
naijauser May 19, 2026
1036d64
add url
naijauser May 19, 2026
5b39cda
update
naijauser May 19, 2026
d3f416b
replace logic with helper
naijauser May 19, 2026
3314348
await execute block
naijauser May 19, 2026
4032ea7
add types
naijauser May 19, 2026
e7e8832
fmt
naijauser May 19, 2026
6aa5946
move live_state resolution into run function
naijauser May 19, 2026
0b6a87c
refactor
naijauser May 19, 2026
171905a
update state type
naijauser May 19, 2026
e276b24
add ref
naijauser May 19, 2026
8779c38
resolve ref issues
naijauser May 19, 2026
ff15397
complete initial logic to execute block from to =to
naijauser May 19, 2026
e1037d7
log block hash
naijauser May 21, 2026
26fe550
fetch hash list and log
naijauser May 21, 2026
18d52ca
loop through hash list
naijauser May 21, 2026
ea5f3e2
fix
naijauser May 21, 2026
af080f6
update
naijauser May 21, 2026
8764f64
uncomment actual execute block logic
naijauser May 21, 2026
53cf1c6
borrow rpc instead of owning
naijauser May 21, 2026
e0c6f01
update execute_block to take references, only move live state
naijauser May 21, 2026
9907167
update
naijauser May 21, 2026
4a9dd1c
update
naijauser May 21, 2026
6071c8a
await execute block inside loop
naijauser May 21, 2026
f809e8b
update log
naijauser May 21, 2026
bf205e5
cargo fmt
naijauser May 21, 2026
0e4fb1d
add new test case
naijauser May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions cli/tests/execute_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,45 @@ async fn execute_block_works() {
.status
.success());
})
.await;

// Test passing --from and --to to execute a range of blocks.
common::run_with_timeout(Duration::from_secs(120), async move {
let ws_url = format!("ws://localhost:{}", port);
let from = 3u64;
let to = 5u64;

fn execute_block_range(ws_url: &str, from: u64, to: u64) -> tokio::process::Child {
Command::new(cargo_bin("try-runtime"))
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.arg("--runtime=existing")
.args(["execute-block"])
.args([format!("--from={}", from), format!("--to={}", to)])
.args(["live", format!("--uri={}", ws_url).as_str()])
.kill_on_drop(true)
.spawn()
.unwrap()
}

let mut block_execution = execute_block_range(&ws_url, from, to);

// Expect the last block in the range to be successfully executed.
let expected_output = format!(r#".*Block #{} successfully executed"#, to);
let re = Regex::new(expected_output.as_str()).unwrap();
let matched =
common::wait_for_stream_pattern_match(block_execution.stderr.take().unwrap(), re).await;

// Assert that all blocks in the range were executed.
assert!(matched.is_ok());

// Assert that the block-execution exited successfully.
assert!(block_execution
.wait_with_output()
.await
.unwrap()
.status
.success());
})
.await
}
122 changes: 102 additions & 20 deletions core/src/commands/execute_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
use std::{fmt::Debug, str::FromStr};

use parity_scale_codec::Encode;
use sc_executor::sp_wasm_interface::HostFunctions;
use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor};
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
use sp_runtime::{
generic::SignedBlock,
traits::{Block as BlockT, Header as HeaderT, NumberFor},
};
use substrate_rpc_client::{ws_client, ChainApi};
use substrate_rpc_client::{ws_client, ChainApi, WsClient};

use crate::{
common::state::{
Expand All @@ -50,6 +51,14 @@ pub struct Command {
#[arg(long, default_value = "all")]
pub try_state: frame_try_runtime::TryStateSelect,

/// Block number to start execution from.
#[arg(long, requires = "to")]
pub from: Option<u64>,

/// Block number to stop execution at.
#[arg(long, requires = "from")]
pub to: Option<u64>,

/// The ws uri from which to fetch the block.
///
/// This will always fetch the next block of whatever `state` is referring to, because this is
Expand Down Expand Up @@ -98,33 +107,106 @@ where
let block_ws_uri = command.block_ws_uri();
let rpc = ws_client(&block_ws_uri).await?;

let live_state = match command.state {
State::Live(live_state) => {
// If no --at is provided, get the latest block to replay
if live_state.at.is_some() {
live_state
} else {
// If --from and --to is passed, they take precedence over LiveState --at.
if let (Some(from), Some(to)) = (command.from, command.to) {
if from > to {
return Err(sc_cli::Error::Application(
format!("--from ({from}) must be less than or equal to --to ({to})").into(),
));
}

let block_numbers = (from..=to)
.map(|n| NumberOrHex::Number(n))
.collect::<Vec<_>>();

let hash_list = ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block_hash(
&rpc,
Some(ListOrValue::List(block_numbers)),
)
.await
.map_err(rpc_err_handler)?;

if let ListOrValue::List(hashes) = hash_list {
for (block_number, hash) in (from..=to).zip(hashes) {
let Some(hash) = hash else {
log::warn!(target: LOG_TARGET, "skipping block {block_number}, hash was None");
continue;
};

log::info!(target: LOG_TARGET, "hash found, block number: {block_number}, hash: {hash}");

let header =
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::header(
&rpc, None,
&rpc,
Some(hash),
)
.await
.map_err(rpc_err_handler)?
.expect("header exists, block should also exist; qed");
LiveState {
uri: vec![block_ws_uri],
.expect("hash exists, header should exist;");

let live_state = LiveState {
uri: vec![block_ws_uri.clone()],
at: Some(hex::encode(header.hash().encode())),
pallet: Default::default(),
hashed_prefixes: Default::default(),
child_tree: Default::default(),
}
};

let _ =
execute_block::<Block, HostFns>(&shared, &command, &executor, &rpc, live_state)
.await;
}
}
_ => {
unreachable!("execute block currently only supports Live state")
}
};

Ok(())
} else {
let live_state = match &command.state {
State::Live(live_state) => {
// If no --at is provided, get the latest block to replay
if live_state.at.is_some() {
live_state.clone()
} else {
let header =
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::header(
&rpc, None,
)
.await
.map_err(rpc_err_handler)?
.expect("header exists, block should also exist; qed");
LiveState {
uri: vec![block_ws_uri],
at: Some(hex::encode(header.hash().encode())),
pallet: Default::default(),
hashed_prefixes: Default::default(),
child_tree: Default::default(),
}
}
}
_ => {
unreachable!("execute block currently only supports Live state")
}
};

execute_block::<Block, HostFns>(&shared, &command, &executor, &rpc, live_state).await
}
}

// Perform block execution on live state
pub async fn execute_block<Block, HostFns>(
shared: &SharedParams,
command: &Command,
executor: &WasmExecutor<HostFns>,
rpc: &WsClient,
live_state: LiveState,
) -> sc_cli::Result<()>
where
Block: BlockT + serde::de::DeserializeOwned,
<Block::Hash as FromStr>::Err: Debug,
Block::Hash: serde::de::DeserializeOwned,
Block::Header: serde::de::DeserializeOwned,
<NumberFor<Block> as TryInto<u64>>::Error: Debug,
HostFns: HostFunctions,
{
// The block we want to *execute* at is the block passed by the user
let execute_at = live_state.at::<Block>()?;

Expand All @@ -142,7 +224,7 @@ where

// Execute the desired block on top of it
let block =
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block(&rpc, execute_at)
ChainApi::<(), Block::Hash, Block::Header, SignedBlock<Block>>::block(rpc, execute_at)
.await
.map_err(rpc_err_handler)?
.expect("header exists, block should also exist; qed")
Expand All @@ -161,7 +243,7 @@ where
block.clone(),
state_root_check,
signature_check,
command.try_state,
command.try_state.clone(),
)
.encode();

Expand All @@ -172,7 +254,7 @@ where
"TryRuntime_execute_block",
&payload,
full_extensions(executor.clone()),
shared.export_proof,
shared.export_proof.clone(),
)?;

Ok(())
Expand Down